]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/commitdiff
Move Clear and Exit to the top of the navigation menu. https://redmine.stoutner...
authorSoren Stoutner <soren@stoutner.com>
Wed, 20 Mar 2019 20:32:36 +0000 (13:32 -0700)
committerSoren Stoutner <soren@stoutner.com>
Wed, 20 Mar 2019 20:32:36 +0000 (13:32 -0700)
86 files changed:
.idea/assetWizardSettings.xml
.idea/dictionaries/soren.xml
app/build.gradle
app/src/free/assets/de/about_permissions_dark.html
app/src/free/assets/de/about_permissions_light.html
app/src/free/assets/de/about_privacy_policy_dark.html
app/src/free/assets/de/about_privacy_policy_light.html
app/src/free/assets/it/about_permissions_dark.html
app/src/free/assets/it/about_permissions_light.html
app/src/free/assets/ru/about_permissions_dark.html
app/src/free/assets/ru/about_permissions_light.html
app/src/main/assets/de/about_contributors_dark.html
app/src/main/assets/de/about_contributors_light.html
app/src/main/assets/de/about_licenses_dark.html
app/src/main/assets/de/about_licenses_light.html
app/src/main/assets/de/about_links_dark.html
app/src/main/assets/de/about_links_light.html
app/src/main/assets/de/about_permissions_dark.html
app/src/main/assets/de/about_permissions_light.html
app/src/main/assets/de/about_privacy_policy_dark.html
app/src/main/assets/de/about_privacy_policy_light.html
app/src/main/assets/de/guide_bookmarks_dark.html
app/src/main/assets/de/guide_bookmarks_light.html
app/src/main/assets/de/guide_domain_settings_dark.html
app/src/main/assets/de/guide_domain_settings_light.html
app/src/main/assets/de/guide_javascript_dark.html
app/src/main/assets/de/guide_javascript_light.html
app/src/main/assets/de/guide_local_storage_dark.html
app/src/main/assets/de/guide_local_storage_light.html
app/src/main/assets/de/guide_overview_dark.html
app/src/main/assets/de/guide_overview_light.html
app/src/main/assets/de/guide_requests_dark.html
app/src/main/assets/de/guide_requests_light.html
app/src/main/assets/de/guide_ssl_certificates_dark.html
app/src/main/assets/de/guide_ssl_certificates_light.html
app/src/main/assets/de/guide_tor_dark.html
app/src/main/assets/de/guide_tor_light.html
app/src/main/assets/de/guide_tracking_ids_dark.html
app/src/main/assets/de/guide_tracking_ids_light.html
app/src/main/assets/de/guide_user_agent_dark.html
app/src/main/assets/de/guide_user_agent_light.html
app/src/main/assets/en/about_licenses_dark.html
app/src/main/assets/en/about_licenses_light.html
app/src/main/assets/en/guide_ssl_certificates_dark.html
app/src/main/assets/en/guide_ssl_certificates_light.html
app/src/main/assets/es/about_licenses_dark.html
app/src/main/assets/es/about_licenses_light.html
app/src/main/assets/it/about_licenses_dark.html
app/src/main/assets/it/about_licenses_light.html
app/src/main/assets/ru/about_licenses_dark.html
app/src/main/assets/ru/about_licenses_light.html
app/src/main/assets/shared_images/tab_dark.png [new file with mode: 0644]
app/src/main/assets/shared_images/tab_light.png [new file with mode: 0644]
app/src/main/assets/tr/about_licenses_dark.html
app/src/main/assets/tr/about_licenses_light.html
app/src/main/java/com/stoutner/privacybrowser/activities/AboutActivity.java
app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksActivity.java
app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
app/src/main/java/com/stoutner/privacybrowser/dialogs/AddDomainDialog.java
app/src/main/java/com/stoutner/privacybrowser/fragments/AboutTabFragment.java
app/src/main/java/com/stoutner/privacybrowser/fragments/WebViewTabFragment.java [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/helpers/BlockListHelper.java
app/src/main/res/drawable/tab.xml [new file with mode: 0644]
app/src/main/res/drawable/theme_dark.xml
app/src/main/res/drawable/theme_light.xml
app/src/main/res/layout/about_coordinatorlayout.xml
app/src/main/res/layout/custom_tab_view.xml [new file with mode: 0644]
app/src/main/res/layout/main_framelayout.xml
app/src/main/res/layout/nestedscroll_webview.xml [new file with mode: 0644]
app/src/main/res/layout/url_app_bar.xml
app/src/main/res/menu/webview_navigation_menu.xml
app/src/main/res/values-de/strings.xml
app/src/main/res/values-es/strings.xml
app/src/main/res/values-it/strings.xml
app/src/main/res/values-ru/strings.xml
app/src/main/res/values-tr/strings.xml
app/src/main/res/values/strings.xml
fastlane/metadata/android/de-DE/full_description.txt [new file with mode: 0644]
fastlane/metadata/android/de-DE/short_description.txt [new file with mode: 0644]
fastlane/metadata/android/de-DE/title.txt [new file with mode: 0644]
fastlane/metadata/android/en-US/full_description.txt
fastlane/metadata/android/en-US/short_description.txt
fastlane/metadata/android/en-US/title.txt
fastlane/metadata/android/tr-TR/full_description.txt [new file with mode: 0644]
fastlane/metadata/android/tr-TR/short_description.txt [new file with mode: 0644]
fastlane/metadata/android/tr-TR/title.txt [new file with mode: 0644]

index a2c18eb6ed7be02627a6e4e1ce48662331a3d305..e1c83ef8f36dce8e89dc57d79f9b1b975e7c46a7 100644 (file)
@@ -68,7 +68,7 @@
                                 <PersistentState>
                                   <option name="values">
                                     <map>
-                                      <entry key="url" value="jar:file:/home/soren/Android/android-studio/plugins/android/lib/android.jar!/images/material_design_icons/action/ic_payment_black_24dp.xml" />
+                                      <entry key="url" value="jar:file:/home/soren/Android/android-studio/plugins/android/lib/android.jar!/images/material_design_icons/action/ic_tab_black_24dp.xml" />
                                     </map>
                                   </option>
                                 </PersistentState>
@@ -78,7 +78,8 @@
                         </option>
                         <option name="values">
                           <map>
-                            <entry key="outputName" value="app_bar" />
+                            <entry key="autoMirrored" value="true" />
+                            <entry key="outputName" value="tab" />
                             <entry key="sourceFile" value="$USER_HOME$/ownCloud/Android/Privacy Browser/Icons/Icons/file_copy_light.svg" />
                           </map>
                         </option>
index e0dfdc07ce6af732496d21bf276ee8725a3d7228..36ddf6a6e96777955a591c1625b08146cf5edc6f 100644 (file)
       <w>mitm</w>
       <w>mozilla</w>
       <w>navigationview</w>
+      <w>nestedscroll</w>
       <w>nightmode</w>
       <w>nist</w>
       <w>nojs</w>
       <w>webpage</w>
       <w>websocket</w>
       <w>webview</w>
+      <w>webviewpager</w>
       <w>whatismyip</w>
       <w>wipo</w>
       <w>wouldn</w>
index 52f4d9b96b1f1d4fac3e178e6ba48772690fb09a..6de6e3bb5855e7d1c1ebaa9eb39e3ee5659c13ee 100644 (file)
@@ -86,5 +86,5 @@ dependencies {
     implementation 'com.google.android.material:material:1.0.0'
 
     // Only compile Firebase ads for the free flavor.
-    freeImplementation 'com.google.firebase:firebase-ads:17.1.3'
+    freeImplementation 'com.google.firebase:firebase-ads:17.2.0'
 }
\ No newline at end of file
index 06aa30163219536d83d796e6d13710044c6f0b91..4d429bce30e341cc01212dd3b50eb5e7063b15f7 100644 (file)
@@ -1,6 +1,8 @@
 <!--
   Copyright © 2016-2019 Soren Stoutner <soren@stoutner.com>.
 
+  Translation 2019 Bernhard G. Keller.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+
   Translation 2018 Stefan Erhardt.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
 
   Translation 2016 Aaron Gerlach <aaron@gerlach.com>.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
         <p><a href="https://developer.android.com/reference/android/Manifest.permission.html#INSTALL_SHORTCUT">com.android.launcher.permission.INSTALL_SHORTCUT</a></p>
         <p>Benötigt, um Verknüpfungen zu Websites auf Ihrer Startseite zu erstellen.</p>
 
-        <h3>Read storage</h3>
+        <h3>Speicher lesen</h3>
         <p><a href="https://developer.android.com/reference/android/Manifest.permission#READ_EXTERNAL_STORAGE">android.permission.READ_EXTERNAL_STORAGE</a></p>
-        <p>Required to import settings from public folders. On Android Marshmallow (API 23) and newer, if this permission is denied Privacy Browser can import settings from the app’s folders instead.</p>
+        <p>Benötigt, um Einstellungen aus öffentlichen Ordnern zu importieren.
+            Unter Android Marshmallow (API 23) und neuer kann Privacy Browser Einstellungen nur aus seinem eigenen Ordner importieren, wenn diese Berechtigung nicht erteilt wird.</p>
 
-        <h3>Write storage</h3>
+        <h3>Speicher schreiben</h3>
         <p><a href="https://developer.android.com/reference/android/Manifest.permission.html#WRITE_EXTERNAL_STORAGE">android.permission.WRITE_EXTERNAL_STORAGE</a></p>
-        <p>Required to export settings and download files to the public folders.
-            On Android Marshmallow (API 23) and newer, if this permission is denied Privacy Browser can export settings and store downloads in the app’s folders instead.</p>
+        <p>Benötigt, um Einstellungen und Downloads in öffentlichen Ordnern zu speichern.
+            Unter Android Marshmallow (API 23) und neuer kann Privacy Browser Einstellungen und Downloads nur in seinem eigenen Ordner speichern, wenn diese Berechtigung nicht erteilt wird.</p>
 
         <br/>
         <hr/>
         <br/>
 
-        <p>In addition, Privacy Browser Free displays ads from Google’s AdMob network using the Firebase backend.
-            For the free flavor, Firebase adds the following permissions even though they are not listed in the source code
-            <a href="https://git.stoutner.com/?p=PrivacyBrowser.git;a=blob;f=app/src/main/AndroidManifest.xml;hb=HEAD">manifest file</a>.</p>
+               <p>Zusätzlich zu den oben genannten Berechtigungen zeigt Privacy Browser Free Werbeanzeigen von Google's AdMob-Netzwerk unter Zuhilfenahme des Firebase-Backends.
+                       Für die "Kostenlos"-Plakette ergänzt Firebase die Liste der Berechtigungen um die Folgenden,
+            obwohl diese nicht im Quellcode der <a href="https://git.stoutner.com/?p=PrivacyBrowser.git;a=blob;f=app/src/main/AndroidManifest.xml;hb=HEAD">Manifest-Datei</a>
+            von Privacy Browser aufgeführt werden:</p>
 
-        <h3>View network connections</h3>
+        <h3>Netzwerk-Verbindungen anzeigen</h3>
         <p><a href="https://developer.android.com/reference/android/Manifest.permission.html#ACCESS_NETWORK_STATE">android.permission.ACCESS_NETWORK_STATE</a></p>
-        <p>Allows the ads to tell when you are connected to the internet and when you aren’t (presumably so they don’t try to reload an ad when you are disconnected).
-            They can also tell if you are connected via Wi-Fi, 2G, 3G, 4G, etc.</p>
+        <p>Erlaubt den Werbeanzeigen mitzuteilen, wenn das Gerät mit dem Internet verbunden ist und wann nicht (vermutlich damit nicht versucht wird, Werbeanzeigen neu zu laden, wenn keine Verbindung besteht).
+                       Diese können auch mitteilen, ob eine Verbindung via WLAN, 2G, 3G, 4G, usw. besteht.</p>
 
-        <h3>Prevent phone from sleeping</h3>
+        <h3>Schlaf-Modus des Telefons verhindern</h3>
         <p><a href="https://developer.android.com/reference/android/Manifest.permission.html#WAKE_LOCK">android.permission.WAKE_LOCK</a></p>
-        <p>Allows the ads to keep the processor from sleeping and the screen from dimming, although in my testing I don’t think the ads actually do this.</p>
+        <p>Erlaubt den Werbeanzeigen, das Gerät nicht in den Schlaf-Modus zu versetzen und das Display nicht zu dimmen. In Stoutners Tests konnte dieses Verhalten nicht festgestellt werden.</p>
 
         <h3>Play Install Referrer API</h3>
         <p><a href="https://android-developers.googleblog.com/2017/11/google-play-referrer-api-track-and.html">com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE</a></p>
-        <p>Allows other apps to tell if their installation was launched from an ad in Privacy Browser Free.</p>
+        <p>Erlaubt anderen Apps mitzuteilen, ob ihre Installation über eine Werbeanzeige innerhalb von Privacy Browser Free ausgelöst wurde.</p>
 
-        <h3>Receive data from Internet</h3>
+        <h3>Daten aus Internet empfangen</h3>
         <p><a href="http://androidpermissions.com/permission/com.google.android.c2dm.permission.RECEIVE">com.google.android.c2dm.permission.RECEIVE</a></p>
-        <p>Allows Google to send information directly to the AdView without having to receive a request first (cloud-to-device messaging).</p>
+        <p>Erlaubt Google Informationen direkt an AdView zu senden, ohne dass zuvor eine entsprechende Anfrage getätigt wurde (cloud-to-device messaging).</p>
 
-        <h3>Receive data from Internet</h3>
+        <h3>Daten aus Internet empfangen</h3>
         <p><a href="https://developers.google.com/cloud-messaging/android/client">com.stoutner.privacybrowser.free.permission.C2D_MESSAGE</a></p>
-        <p>Secures the cloud-to-device messages so that only Privacy Browser Free can receive them.</p>
+        <p>Sichert die cloud-to-device-Nachrichten ab, damit nur Privacy Browser Free diese empfangen kann.</p>
     </body>
 </html>
\ No newline at end of file
index ccea38eb73f5e0657a72e4291cd57310da22ae4b..235cb4e2ae0a289491cbb52fe036c900ed50433a 100644 (file)
@@ -1,6 +1,8 @@
 <!--
   Copyright © 2016-2019 Soren Stoutner <soren@stoutner.com>.
 
+  Translation 2019 Bernhard G. Keller.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+
   Translation 2018 Stefan Erhardt.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
 
   Translation 2016 Aaron Gerlach <aaron@gerlach.com>.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
         <p><a href="https://developer.android.com/reference/android/Manifest.permission.html#INSTALL_SHORTCUT">com.android.launcher.permission.INSTALL_SHORTCUT</a></p>
         <p>Benötigt, um Verknüpfungen zu Websites auf Ihrer Startseite zu erstellen.</p>
 
-        <h3>Read storage</h3>
+        <h3>Speicher lesen</h3>
         <p><a href="https://developer.android.com/reference/android/Manifest.permission#READ_EXTERNAL_STORAGE">android.permission.READ_EXTERNAL_STORAGE</a></p>
-        <p>Required to import settings from public folders. On Android Marshmallow (API 23) and newer, if this permission is denied Privacy Browser can import settings from the app’s folders instead.</p>
+        <p>Benötigt, um Einstellungen aus öffentlichen Ordnern zu importieren.
+            Unter Android Marshmallow (API 23) und neuer kann Privacy Browser Einstellungen nur aus seinem eigenen Ordner importieren, wenn diese Berechtigung nicht erteilt wird.</p>
 
-        <h3>Write storage</h3>
+        <h3>Speicher schreiben</h3>
         <p><a href="https://developer.android.com/reference/android/Manifest.permission.html#WRITE_EXTERNAL_STORAGE">android.permission.WRITE_EXTERNAL_STORAGE</a></p>
-        <p>Required to export settings and download files to the public folders.
-            On Android Marshmallow (API 23) and newer, if this permission is denied Privacy Browser can export settings and store downloads in the app’s folders instead.</p>
+        <p>Benötigt, um Einstellungen und Downloads in öffentlichen Ordnern zu speichern.
+            Unter Android Marshmallow (API 23) und neuer kann Privacy Browser Einstellungen und Downloads nur in seinem eigenen Ordner speichern, wenn diese Berechtigung nicht erteilt wird.</p>
 
         <br/>
         <hr/>
         <br/>
 
-        <p>In addition, Privacy Browser Free displays ads from Google’s AdMob network using the Firebase backend.
-            For the free flavor, Firebase adds the following permissions even though they are not listed in the source code
-            <a href="https://git.stoutner.com/?p=PrivacyBrowser.git;a=blob;f=app/src/main/AndroidManifest.xml;hb=HEAD">manifest file</a>.</p>
+        <p>Zusätzlich zu den oben genannten Berechtigungen zeigt Privacy Browser Free Werbeanzeigen von Google's AdMob-Netzwerk unter Zuhilfenahme des Firebase-Backends.
+            Für die "Kostenlos"-Plakette ergänzt Firebase die Liste der Berechtigungen um die Folgenden,
+            obwohl diese nicht im Quellcode der <a href="https://git.stoutner.com/?p=PrivacyBrowser.git;a=blob;f=app/src/main/AndroidManifest.xml;hb=HEAD">Manifest-Datei</a>
+            von Privacy Browser aufgeführt werden:</p>
 
-        <h3>View network connections</h3>
+        <h3>Netzwerk-Verbindungen anzeigen</h3>
         <p><a href="https://developer.android.com/reference/android/Manifest.permission.html#ACCESS_NETWORK_STATE">android.permission.ACCESS_NETWORK_STATE</a></p>
-        <p>Allows the ads to tell when you are connected to the internet and when you aren’t (presumably so they don’t try to reload an ad when you are disconnected).
-            They can also tell if you are connected via Wi-Fi, 2G, 3G, 4G, etc.</p>
+        <p>Erlaubt den Werbeanzeigen mitzuteilen, wenn das Gerät mit dem Internet verbunden ist und wann nicht (vermutlich damit nicht versucht wird, Werbeanzeigen neu zu laden, wenn keine Verbindung besteht).
+            Diese können auch mitteilen, ob eine Verbindung via WLAN, 2G, 3G, 4G, usw. besteht.</p>
 
-        <h3>Prevent phone from sleeping</h3>
+        <h3>Schlaf-Modus des Telefons verhindern</h3>
         <p><a href="https://developer.android.com/reference/android/Manifest.permission.html#WAKE_LOCK">android.permission.WAKE_LOCK</a></p>
-        <p>Allows the ads to keep the processor from sleeping and the screen from dimming, although in my testing I don’t think the ads actually do this.</p>
+        <p>Erlaubt den Werbeanzeigen, das Gerät nicht in den Schlaf-Modus zu versetzen und das Display nicht zu dimmen. In Stoutners Tests konnte dieses Verhalten nicht festgestellt werden.</p>
 
         <h3>Play Install Referrer API</h3>
         <p><a href="https://android-developers.googleblog.com/2017/11/google-play-referrer-api-track-and.html">com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE</a></p>
-        <p>Allows other apps to tell if their installation was launched from an ad in Privacy Browser Free.</p>
+        <p>Erlaubt anderen Apps mitzuteilen, ob ihre Installation über eine Werbeanzeige innerhalb von Privacy Browser Free ausgelöst wurde.</p>
 
-        <h3>Receive data from Internet</h3>
+        <h3>Daten aus Internet empfangen</h3>
         <p><a href="http://androidpermissions.com/permission/com.google.android.c2dm.permission.RECEIVE">com.google.android.c2dm.permission.RECEIVE</a></p>
-        <p>Allows Google to send information directly to the AdView without having to receive a request first (cloud-to-device messaging).</p>
+        <p>Erlaubt Google Informationen direkt an AdView zu senden, ohne dass zuvor eine entsprechende Anfrage getätigt wurde (cloud-to-device messaging).</p>
 
-        <h3>Receive data from Internet</h3>
+        <h3>Daten aus Internet empfangen</h3>
         <p><a href="https://developers.google.com/cloud-messaging/android/client">com.stoutner.privacybrowser.free.permission.C2D_MESSAGE</a></p>
-        <p>Secures the cloud-to-device messages so that only Privacy Browser Free can receive them.</p>
+        <p>Sichert die cloud-to-device-Nachrichten ab, damit nur Privacy Browser Free diese empfangen kann.</p>
     </body>
 </html>
\ No newline at end of file
index cb188bec228ad72ac97336435615718f6412584c..fdcf94b595f78ebfdcf3daf54ed9d2265174ead8 100644 (file)
@@ -1,6 +1,8 @@
 <!--
   Copyright © 2016-2018 Soren Stoutner <soren@stoutner.com>.
 
+  Translation 2019 Bernhard G. Keller.  Copyright assigned to 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
 
     <body>
         <h3>Privacy Browser Free</h3>
-        <p><strong class="red">Privacy Browser Free does not collect any user information</strong>.</p>
-
+        <p><strong class="red">Privacy Browser Free sammelt keinerlei Benutzer-Informationen.</strong></p>
 
         <h3>Google Play</h3>
-        <p>Google Play has its <a href="https://www.google.com/intl/en/policies/privacy/">own privacy policy</a>.
-            Google provides <em>anonymized summary installation information</em> to developers, including the number of installs organized by the following categories.</p>
+        <p>Google Play hat seine eigene <a href="https://policies.google.com/privacy?hl=de">Datenschutzerklärung</a>.
+                       Google stellt Entwicklern <em>anonymisierte Installations-Informationen</em> inklusive der Anzahl der Installationen unterteilt in die folgenden Kategorien bereit:</p>
         <ul>
-            <li><item>Android version</item> (eg. Android 7.1)</li>
-            <li><item>Device</item> (eg. Samsung Galaxy S6 [zeroflte])</li>
-            <li><item>Tablets</item> (eg. Tablets 10" and above)</li>
-            <li><item>Country</item> (eg. United States)</li>
-            <li><item>Language</item> (eg. English [United States])</li>
-            <li><item>App version</item> (eg. 14)</li>
-            <li><item>Carrier</item> (eg. T-Mobile - US)</li>
+            <li><item>Android-Version</item> (z.B. Android 7.1)</li>
+            <li><item>Gerät</item> (z.B. Samsung Galaxy S6 [zeroflte])</li>
+            <li><item>Tablets</item> (z.B. 10"-Tablet oder höher)</li>
+            <li><item>Land</item> (z.B. USA)</li>
+            <li><item>Sprache</item> (z.B. Englisch [USA])</li>
+            <li><item>App-Version</item> (z.B. 14)</li>
+            <li><item>Mobilfunk-Anbieter</item> (z.B. T-Mobile - US)</li>
         </ul>
 
 
-        <h3>Google Play Ratings</h3>
-        <p>Google Play has its <a href="https://www.google.com/intl/en/policies/privacy/">own privacy policy</a>.
-            Google provides developers with <em>anonymized summaries</em> of the following information related to user ratings.</p>
+        <h3>Google Play Bewertungen</h3>
+        <p>Google Play hat seine eigene <a href="https://policies.google.com/privacy?hl=de">Datenschutzerklärung</a>.
+                       Google stellt Entwicklern folgende <em>anonymisierte Installations-Informationen</em> bezüglich der Bewertungen bereit:</p>
         <ul>
-            <li><item>Country</item> (eg. United States)</li>
-            <li><item>Language</item> (eg. English)</li>
-            <li><item>App version</item> (eg. 14)</li>
-            <li><item>Android version</item> (eg. Android 7.1)</li>
-            <li><item>Device</item> (eg. Google Nexus 5X [bullhead])</li>
-            <li><item>Tablets</item> (eg. Tablets 10" and above)</li>
+            <li><item>Land</item> (z.B. USA)</li>
+            <li><item>Sprache</item> (z.B. Englisch)</li>
+            <li><item>App-Version</item> (z.B. 14)</li>
+            <li><item>Android-Version</item> (z.B. Android 7.1)</li>
+            <li><item>Gerät</item> (z.B. Google Nexus 5X [bullhead])</li>
+            <li><item>Tablets</item> (z.B. 10"-Tablet oder höher)</li>
         </ul>
 
 
-        <h3>Google Play Reviews</h3>
-        <p>Google Play has its <a href="https://www.google.com/intl/en/policies/privacy/">own privacy policy</a>.
-            In addition to the name of the reviewer, the rating, and the text of the review (which are all available publicly), Google provides some or all of the following information to the developer.</p>
+        <h3>Google Play Rezensionen</h3>
+        <p>Google Play hat seine eigene <a href="https://policies.google.com/privacy?hl=de">Datenschutzerklärung</a>.
+                       Zusätzlich zum Namen des Rezensenten, der Bewertung und dem Text der Rezension (welche alle öffentlich zugänglich sind),
+            stellt Google dem Entwickler einige oder alle der folgenden Informationen zur Verfügung:</p>
         <ul>
-            <li><item>Version code</item> (eg. 7)</li>
-            <li><item>Version name</item> (eg. 1.6)</li>
-            <li><item>Android version</item> (eg. Android 5.1)</li>
-            <li><item>Device</item> (eg. Galaxy S6 Edge+ [zenlte])</li>
-            <li><item>Manufacturer</item> (eg. Samsung)</li>
-            <li><item>Device type</item> (eg. Phone)</li>
-            <li><item>CPU make</item> (eg. Samsung)</li>
-            <li><item>CPU model</item> (eg. Exynos 7420)</li>
-            <li><item>Screen density</item> (eg. 560 dpi)</li>
-            <li><item>Screen size</item> (eg. 2560 x 1440)</li>
-            <li><item>RAM</item> (eg. 4096 MB)</li>
-            <li><item>Native platform</item> (eg. armeabi-v7a,armeabi,arm64v8a)</li>
-            <li><item>OpenGL ES version</item> (eg. 3.1)</li>
-            <li><item>Device language</item> (eg. English)</li>
+            <li><item>Versions-Code</item> (z.B. 7)</li>
+            <li><item>Versions-Name</item> (z.B. 1.6)</li>
+            <li><item>Android-Version</item> (z.B. Android 5.1)</li>
+            <li><item>Gerät</item> (z.B. Galaxy S6 Edge+ [zenlte])</li>
+            <li><item>Hersteller</item> (z.B. Samsung)</li>
+            <li><item>Gerätetyp</item> (z.B. Mobiltelefon)</li>
+            <li><item>CPU-Fabrikat</item> (z.B. Samsung)</li>
+            <li><item>CPU-Model</item> (z.B. Exynos 7420)</li>
+            <li><item>Bildschirm-Auflösung</item> (z.B. 560 dpi)</li>
+            <li><item>Bildschirm-Grösse</item> (z.B. 2560 x 1440)</li>
+            <li><item>RAM</item> (z.B. 4096 MB)</li>
+            <li><item>Native Plattform</item> (z.B. armeabi-v7a,armeabi,arm64v8a)</li>
+            <li><item>OpenGL ES Version</item> (z.B. 3.1)</li>
+            <li><item>Geräte-Sprache</item> (z.B. Englisch)</li>
         </ul>
 
 
-        <h3>Advertisements</h3>
-        <p>Privacy Browser Free einen Werbebanner unten am Bildschirm ein und verwendet hierfür Googles Netzwerk AdMob,
+        <h3>Werbeeinblendungen</h3>
+        <p>Privacy Browser Free blendet einen Werbebanner unten am Bildschirm ein und verwendet hierfür Googles Netzwerk AdMob,
             dessen eigene Datenschutzbestimmungen <a href="https://www.google.com/intl/en/policies/privacy/">hier zu finden sind</a>.
             Diese Werbung wurde eingestellt als anonymisiert und der Benutzer ist als unter dem Schutzalter gesetzt,
             was sämtliches <a href="https://developers.google.com/admob/android/eu-consent#collect_consent">Tracking und Remarketing</a> deaktiviert.
-            AdMob überträgt <em>anonymisierte Zusammenfassungen</em> folgender Informationen an die Entwickler.</p>
+            AdMob überträgt <em>anonymisierte Zusammenfassungen</em> folgender Informationen an die Entwickler:</p>
         <ul>
             <li><item>Totale Besuche</item></li>
             <li><item>Totale Klicks</item></li>
-            <li><item>Platform</item> (z.B. high-end Smartphones, Tablets)</li>
-            <li><item>Activitäten nach Ländern</item></li>
+            <li><item>Platform</item> (z.B. High-End Smartphones, Tablets)</li>
+            <li><item>Aktivitäten nach Ländern</item></li>
         </ul>
 
 
-        <h3>Direct Communications</h3>
-        <p>Users may choose to send direct communications to Stoutner, like email messages and comments on <a href="https://www.stoutner.com/">stoutner.com</a>.</p>
-
+         <h3>Direkte Kommunikation</h3>
+        <p>Benutzer können z.B. per Email und Kommentaren auf <a href="https://www.stoutner.com/">stoutner.com</a> direkt mit Stoutner Kontakt aufnehmen.</p>
 
-        <h3>Use of Information</h3>
-        <p><strong class="blue">Stoutner may use this information to assist in the development of Privacy Browser and communicate the status of the project to users.</strong>
-            <strong class="red">Stoutner will never sell this information nor transfer it to any third party that would use it for advertising or marketing.</strong></p>
+        <h3>Daten-Nutzung</h3>
+        <p><strong class="blue">Stoutner kann diese Informationen nutzen, um die Entwicklung von Privacy Browser zu unterstützen und den Status des Projekts an Benutzer zu melden.</strong>
+            <strong class="red">Stoutner wird die Informationen niemals verkaufen oder an Dritte weitergeben, welche diese für Anzeigen oder Marketing nutzen.</strong></p>
 
         <hr />
         <p style="text-align: center;"><em>Revision 1.6, 22. Mai 2018</em></p>
index 5a4bb26067f7b6ebc99b86dc5f2bfd3000d4dc95..dd76b8702bbf5b22d1ab97607dde6ca20c6eeac8 100644 (file)
@@ -1,6 +1,8 @@
 <!--
   Copyright © 2016-2018 Soren Stoutner <soren@stoutner.com>.
 
+  Translation 2019 Bernhard G. Keller.  Copyright assigned to 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
 
     <body>
         <h3>Privacy Browser Free</h3>
-        <p><strong class="red">Privacy Browser Free does not collect any user information</strong>.</p>
-
+        <p><strong class="red">Privacy Browser Free sammelt keinerlei Benutzer-Informationen.</strong></p>
 
         <h3>Google Play</h3>
-        <p>Google Play has its <a href="https://www.google.com/intl/en/policies/privacy/">own privacy policy</a>.
-            Google provides <em>anonymized summary installation information</em> to developers, including the number of installs organized by the following categories.</p>
+        <p>Google Play hat seine eigene <a href="https://policies.google.com/privacy?hl=de">Datenschutzerklärung</a>.
+            Google stellt Entwicklern <em>anonymisierte Installations-Informationen</em> inklusive der Anzahl der Installationen unterteilt in die folgenden Kategorien bereit:</p>
         <ul>
-            <li><item>Android version</item> (eg. Android 7.1)</li>
-            <li><item>Device</item> (eg. Samsung Galaxy S6 [zeroflte])</li>
-            <li><item>Tablets</item> (eg. Tablets 10" and above)</li>
-            <li><item>Country</item> (eg. United States)</li>
-            <li><item>Language</item> (eg. English [United States])</li>
-            <li><item>App version</item> (eg. 14)</li>
-            <li><item>Carrier</item> (eg. T-Mobile - US)</li>
+            <li><item>Android-Version</item> (z.B. Android 7.1)</li>
+            <li><item>Gerät</item> (z.B. Samsung Galaxy S6 [zeroflte])</li>
+            <li><item>Tablets</item> (z.B. 10"-Tablet oder höher)</li>
+            <li><item>Land</item> (z.B. USA)</li>
+            <li><item>Sprache</item> (z.B. Englisch [USA])</li>
+            <li><item>App-Version</item> (z.B. 14)</li>
+            <li><item>Mobilfunk-Anbieter</item> (z.B. T-Mobile - US)</li>
         </ul>
 
 
-        <h3>Google Play Ratings</h3>
-        <p>Google Play has its <a href="https://www.google.com/intl/en/policies/privacy/">own privacy policy</a>.
-            Google provides developers with <em>anonymized summaries</em> of the following information related to user ratings.</p>
+        <h3>Google Play Bewertungen</h3>
+        <p>Google Play hat seine eigene <a href="https://policies.google.com/privacy?hl=de">Datenschutzerklärung</a>.
+            Google stellt Entwicklern folgende <em>anonymisierte Installations-Informationen</em> bezüglich der Bewertungen bereit:</p>
         <ul>
-            <li><item>Country</item> (eg. United States)</li>
-            <li><item>Language</item> (eg. English)</li>
-            <li><item>App version</item> (eg. 14)</li>
-            <li><item>Android version</item> (eg. Android 7.1)</li>
-            <li><item>Device</item> (eg. Google Nexus 5X [bullhead])</li>
-            <li><item>Tablets</item> (eg. Tablets 10" and above)</li>
+            <li><item>Land</item> (z.B. USA)</li>
+            <li><item>Sprache</item> (z.B. Englisch)</li>
+            <li><item>App-Version</item> (z.B. 14)</li>
+            <li><item>Android-Version</item> (z.B. Android 7.1)</li>
+            <li><item>Gerät</item> (z.B. Google Nexus 5X [bullhead])</li>
+            <li><item>Tablets</item> (z.B. 10"-Tablet oder höher)</li>
         </ul>
 
 
-        <h3>Google Play Reviews</h3>
-        <p>Google Play has its <a href="https://www.google.com/intl/en/policies/privacy/">own privacy policy</a>.
-            In addition to the name of the reviewer, the rating, and the text of the review (which are all available publicly), Google provides some or all of the following information to the developer.</p>
+        <h3>Google Play Rezensionen</h3>
+        <p>Google Play hat seine eigene <a href="https://policies.google.com/privacy?hl=de">Datenschutzerklärung</a>.
+            Zusätzlich zum Namen des Rezensenten, der Bewertung und dem Text der Rezension (welche alle öffentlich zugänglich sind),
+            stellt Google dem Entwickler einige oder alle der folgenden Informationen zur Verfügung:</p>
         <ul>
-            <li><item>Version code</item> (eg. 7)</li>
-            <li><item>Version name</item> (eg. 1.6)</li>
-            <li><item>Android version</item> (eg. Android 5.1)</li>
-            <li><item>Device</item> (eg. Galaxy S6 Edge+ [zenlte])</li>
-            <li><item>Manufacturer</item> (eg. Samsung)</li>
-            <li><item>Device type</item> (eg. Phone)</li>
-            <li><item>CPU make</item> (eg. Samsung)</li>
-            <li><item>CPU model</item> (eg. Exynos 7420)</li>
-            <li><item>Screen density</item> (eg. 560 dpi)</li>
-            <li><item>Screen size</item> (eg. 2560 x 1440)</li>
-            <li><item>RAM</item> (eg. 4096 MB)</li>
-            <li><item>Native platform</item> (eg. armeabi-v7a,armeabi,arm64v8a)</li>
-            <li><item>OpenGL ES version</item> (eg. 3.1)</li>
-            <li><item>Device language</item> (eg. English)</li>
+            <li><item>Versions-Code</item> (z.B. 7)</li>
+            <li><item>Versions-Name</item> (z.B. 1.6)</li>
+            <li><item>Android-Version</item> (z.B. Android 5.1)</li>
+            <li><item>Gerät</item> (z.B. Galaxy S6 Edge+ [zenlte])</li>
+            <li><item>Hersteller</item> (z.B. Samsung)</li>
+            <li><item>Gerätetyp</item> (z.B. Mobiltelefon)</li>
+            <li><item>CPU-Fabrikat</item> (z.B. Samsung)</li>
+            <li><item>CPU-Model</item> (z.B. Exynos 7420)</li>
+            <li><item>Bildschirm-Auflösung</item> (z.B. 560 dpi)</li>
+            <li><item>Bildschirm-Grösse</item> (z.B. 2560 x 1440)</li>
+            <li><item>RAM</item> (z.B. 4096 MB)</li>
+            <li><item>Native Plattform</item> (z.B. armeabi-v7a,armeabi,arm64v8a)</li>
+            <li><item>OpenGL ES Version</item> (z.B. 3.1)</li>
+            <li><item>Geräte-Sprache</item> (z.B. Englisch)</li>
         </ul>
 
 
-        <h3>Advertisements</h3>
-        <p>Privacy Browser Free einen Werbebanner unten am Bildschirm ein und verwendet hierfür Googles Netzwerk AdMob,
+        <h3>Werbeeinblendungen</h3>
+        <p>Privacy Browser Free blendet einen Werbebanner unten am Bildschirm ein und verwendet hierfür Googles Netzwerk AdMob,
             dessen eigene Datenschutzbestimmungen <a href="https://www.google.com/intl/en/policies/privacy/">hier zu finden sind</a>.
             Diese Werbung wurde eingestellt als anonymisiert und der Benutzer ist als unter dem Schutzalter gesetzt,
             was sämtliches <a href="https://developers.google.com/admob/android/eu-consent#collect_consent">Tracking und Remarketing</a> deaktiviert.
-            AdMob überträgt <em>anonymisierte Zusammenfassungen</em> folgender Informationen an die Entwickler.</p>
+            AdMob überträgt <em>anonymisierte Zusammenfassungen</em> folgender Informationen an die Entwickler:</p>
         <ul>
             <li><item>Totale Besuche</item></li>
             <li><item>Totale Klicks</item></li>
-            <li><item>Platform</item> (z.B. high-end Smartphones, Tablets)</li>
-            <li><item>Activitäten nach Ländern</item></li>
+            <li><item>Platform</item> (z.B. High-End Smartphones, Tablets)</li>
+            <li><item>Aktivitäten nach Ländern</item></li>
         </ul>
 
 
-        <h3>Direct Communications</h3>
-        <p>Users may choose to send direct communications to Stoutner, like email messages and comments on <a href="https://www.stoutner.com/">stoutner.com</a>.</p>
-
+        <h3>Direkte Kommunikation</h3>
+        <p>Benutzer können z.B. per Email und Kommentaren auf <a href="https://www.stoutner.com/">stoutner.com</a> direkt mit Stoutner Kontakt aufnehmen.</p>
 
-        <h3>Use of Information</h3>
-        <p><strong class="blue">Stoutner may use this information to assist in the development of Privacy Browser and communicate the status of the project to users.</strong>
-            <strong class="red">Stoutner will never sell this information nor transfer it to any third party that would use it for advertising or marketing.</strong></p>
+        <h3>Daten-Nutzung</h3>
+        <p><strong class="blue">Stoutner kann diese Informationen nutzen, um die Entwicklung von Privacy Browser zu unterstützen und den Status des Projekts an Benutzer zu melden.</strong>
+            <strong class="red">Stoutner wird die Informationen niemals verkaufen oder an Dritte weitergeben, welche diese für Anzeigen oder Marketing nutzen.</strong></p>
 
         <hr />
         <p style="text-align: center;"><em>Revision 1.6, 22. Mai 2018</em></p>
index 7f7ae05f84a6bbd7d1c5a555df31e3bf94842e03..7494432edcd6c9678107f40adcd0ba006d293106 100644 (file)
@@ -49,8 +49,8 @@
         <br/>
 
         <p>Oltre ai permessi sopraelencati, Privacy Browser Free mostra gli annunci provenienti dalla Google's AdMob network utilizzando il Firebase backend.
-            For the free flavor, Firebase adds the following permissions even though they are not listed in the source code
-            <a href="https://git.stoutner.com/?p=PrivacyBrowser.git;a=blob;f=app/src/main/AndroidManifest.xml;hb=HEAD">manifest file</a>.</p>
+            Se si utilizza la versione Free, Firebase aggiunge i seguenti permessi anche se non sono elencati nel codice sorgente del file
+            “<a href="https://git.stoutner.com/?p=PrivacyBrowser.git;a=blob;f=app/src/main/AndroidManifest.xml;hb=HEAD">manifest</a>”.</p>
 
         <h3>Visualizzare connessioni di rete</h3>
         <p><a href="https://developer.android.com/reference/android/Manifest.permission.html#ACCESS_NETWORK_STATE">android.permission.ACCESS_NETWORK_STATE</a></p>
index cdf69cf1b3e5d9fea4d56b395382bfdffa24c1c6..75a4fc3ec334e0f9bfd5dd7b091a8659234578b3 100644 (file)
@@ -49,8 +49,8 @@
         <br/>
 
         <p>Oltre ai permessi sopraelencati, Privacy Browser Free mostra gli annunci provenienti dalla Google's AdMob network utilizzando il Firebase backend.
-            For the free flavor, Firebase adds the following permissions even though they are not listed in the source code
-            <a href="https://git.stoutner.com/?p=PrivacyBrowser.git;a=blob;f=app/src/main/AndroidManifest.xml;hb=HEAD">manifest file</a>.</p>
+            Se si utilizza la versione Free, Firebase aggiunge i seguenti permessi anche se non sono elencati nel codice sorgente del file
+            “<a href="https://git.stoutner.com/?p=PrivacyBrowser.git;a=blob;f=app/src/main/AndroidManifest.xml;hb=HEAD">manifest</a>”.</p>
 
         <h3>Visualizzare connessioni di rete</h3>
         <p><a href="https://developer.android.com/reference/android/Manifest.permission.html#ACCESS_NETWORK_STATE">android.permission.ACCESS_NETWORK_STATE</a></p>
index 6c166aee67569937dcc7aa4a80f55f770f383ea6..878238d7ee9ef92e42d75d5035c5f34c793bc06d 100644 (file)
@@ -47,8 +47,8 @@
         <br/>
 
         <p>Кроме того, Privacy Browser Free отображает объявления из сети Google AdMob с использованием бэкенда Firebase.
-            For the free flavor, Firebase adds the following permissions even though they are not listed in the source code
-            <a href="https://git.stoutner.com/?p=PrivacyBrowser.git;a=blob;f=app/src/main/AndroidManifest.xml;hb=HEAD">manifest file</a>.</p>
+            Для бесплатной версии Firebase добавляет следующие разрешения, даже если они не перечислены в
+            <a href="https://git.stoutner.com/?p=PrivacyBrowser.git;a=blob;f=app/src/main/AndroidManifest.xml;hb=HEAD">файле манифеста</a> исходного кода.</p>
 
         <h3>Просмотр сетевых подключений</h3>
         <p><a href="https://developer.android.com/reference/android/Manifest.permission.html#ACCESS_NETWORK_STATE">android.permission.ACCESS_NETWORK_STATE</a></p>
index 96c290a32df3e558f2c6374fd4475525e0bf1b4b..e6e15b89076bb98f16ffc6ba32f48c2e8e6ccd08 100644 (file)
@@ -47,8 +47,8 @@
         <br/>
 
         <p>Кроме того, Privacy Browser Free отображает объявления из сети Google AdMob с использованием бэкенда Firebase.
-            For the free flavor, Firebase adds the following permissions even though they are not listed in the source code
-            <a href="https://git.stoutner.com/?p=PrivacyBrowser.git;a=blob;f=app/src/main/AndroidManifest.xml;hb=HEAD">manifest file</a>.</p>
+            Для бесплатной версии Firebase добавляет следующие разрешения, даже если они не перечислены в
+            <a href="https://git.stoutner.com/?p=PrivacyBrowser.git;a=blob;f=app/src/main/AndroidManifest.xml;hb=HEAD">файле манифеста</a> исходного кода.</p>
 
         <h3>Просмотр сетевых подключений</h3>
         <p><a href="https://developer.android.com/reference/android/Manifest.permission.html#ACCESS_NETWORK_STATE">android.permission.ACCESS_NETWORK_STATE</a></p>
index 7a8a054427d4f3a0734b9144fdb1f9de94d68577..ef561048684ddf988c6df3c6a606e7c471857f21 100644 (file)
@@ -1,6 +1,8 @@
 <!--
   Copyright © 2016-2019 Soren Stoutner <soren@stoutner.com>.
 
+  Translation 2019 Bernhard G. Keller.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+
   Translation 2016 Aaron Gerlach <aaron@gerlach.com>.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
     </head>
 
     <body>
-        <h3>Leitende Entwickler</h3>
-        <p>Privacy Browser ist primär entwickelt von <a href="mailto:soren@stoutner.com">Soren Stoutner</a>.</p>
+        <h3>Leitender Entwickler</h3>
+        <p>Privacy Browser wird primär von <a href="mailto:soren@stoutner.com">Soren Stoutner</a> entwickelt.</p>
 
-        <h3>Coders</h3>
+        <h3>Weitere Entwickler</h3>
         <a href="mailto:lianergoist@vongriffen.dk">Thomas Jensen</a><br/>
         Hendrik Knackstedt
 
         <h3>Mitwirkende</h3>
-        Bernhard G. Keller: Deutsche<br/>
+        Bernhard G. Keller: Deutsch<br/>
         Francesco Buratti: Italienisch<br/>
         Jose A. León: Spanisch
 
-        <h3>Past Translators</h3>
-        Stefan Erhardt: Deutsche<br/>
-        <a href="mailto:aaron@gerlach.com">Aaron Gerlach</a>: Deutsche
+        <h3>Frühere Mitwirkende</h3>
+        Stefan Erhardt: Deutsch<br/>
+        <a href="mailto:aaron@gerlach.com">Aaron Gerlach</a>: Deutsch
 
         <br/>
         <br/>
index 7bc3809a32b54656e8a96168680fba63d9fc972a..4ee41979accd52f966535afb4f91f58cc2c84a53 100644 (file)
@@ -1,6 +1,8 @@
 <!--
   Copyright © 2016-2019 Soren Stoutner <soren@stoutner.com>.
 
+  Translation 2019 Bernhard G. Keller.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+
   Translation 2016 Aaron Gerlach <aaron@gerlach.com>.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
     </head>
 
     <body>
-        <h3>Leitende Entwickler</h3>
-        <p>Privacy Browser ist primär entwickelt von <a href="mailto:soren@stoutner.com">Soren Stoutner</a>.</p>
+        <h3>Leitender Entwickler</h3>
+        <p>Privacy Browser wird primär von <a href="mailto:soren@stoutner.com">Soren Stoutner</a> entwickelt.</p>
 
-        <h3>Coders</h3>
+        <h3>Weitere Entwickler</h3>
         <a href="mailto:lianergoist@vongriffen.dk">Thomas Jensen</a><br/>
         Hendrik Knackstedt
 
         <h3>Mitwirkende</h3>
-        Bernhard G. Keller: Deutsche<br/>
+        Bernhard G. Keller: Deutsch<br/>
         Francesco Buratti: Italienisch<br/>
         Jose A. León: Spanisch
 
-        <h3>Past Translators</h3>
-        Stefan Erhardt: Deutsche<br/>
-        <a href="mailto:aaron@gerlach.com">Aaron Gerlach</a>: Deutsche
+        <h3>Frühere Mitwirkende</h3>
+        Stefan Erhardt: Deutsch<br/>
+        <a href="mailto:aaron@gerlach.com">Aaron Gerlach</a>: Deutsch
 
         <br/>
         <br/>
index 6b5bed0363783b49f5cecfc1988135df92cacd1a..aaaac72f3a1c5a39bc5f020f066f8b16b7883064 100644 (file)
@@ -49,6 +49,7 @@
         <p>Privacy Browser is built with the <a href="https://developer.android.com/jetpack/androidx/">AndroidX Libraries</a>
             and code from the <a href="https://mvnrepository.com/artifact/com.google.android.material/material">Google Material Maven repository</a>,
             which are released under the <a href="https://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>.</p>
+
         <p>The free flavor of Privacy Browser is built with <a href="https://mvnrepository.com/artifact/com.google.firebase/firebase-ads">Firebase Ads</a>,
             which is released under the <a href="https://developer.android.com/studio/terms">Android Software Development Kit License</a>.</p>
 
         <p><img class="icon" src="../shared_images/sort_dark.png"> sort.</p>
         <p><img class="icon" src="../shared_images/style_dark.png"> style.</p>
         <p><img class="icon" src="../shared_images/subtitles_dark.png"> subtitles.</p>
+        <p><img class="icon" src="../shared_images/tab_dark.png"> tab.</p>
         <p><img class="icon" src="../shared_images/text_fields_dark.png"> text_fields.</p>
         <p><img class="icon" src="../shared_images/thumbs_up_down_dark.png"> thumbs_up_down.</p>
         <p><img class="icon" src="../shared_images/vertical_align_bottom_dark.png"> vertical_align_bottom.</p>
index 3d4fdb3cfb77c06d8c8a750a6aa8982668c5753e..69b7a2da91441610d3e938ea3137e569ae3acb01 100644 (file)
@@ -48,6 +48,7 @@
         <p>Privacy Browser is built with the <a href="https://developer.android.com/jetpack/androidx/">AndroidX Libraries</a>
             and code from the <a href="https://mvnrepository.com/artifact/com.google.android.material/material">Google Material Maven repository</a>,
             which are released under the <a href="https://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>.</p>
+
         <p>The free flavor of Privacy Browser is built with <a href="https://mvnrepository.com/artifact/com.google.firebase/firebase-ads">Firebase Ads</a>,
             which is released under the <a href="https://developer.android.com/studio/terms">Android Software Development Kit License</a>.</p>
 
         <p><img class="icon" src="../shared_images/sort_light.png"> sort.</p>
         <p><img class="icon" src="../shared_images/style_light.png"> style.</p>
         <p><img class="icon" src="../shared_images/subtitles_light.png"> subtitles.</p>
+        <p><img class="icon" src="../shared_images/tab_light.png"> tab.</p>
         <p><img class="icon" src="../shared_images/text_fields_light.png"> text_fields.</p>
         <p><img class="icon" src="../shared_images/thumbs_up_down_light.png"> thumbs_up_down.</p>
         <p><img class="icon" src="../shared_images/vertical_align_bottom_light.png"> vertical_align_bottom.</p>
index 0c7f32138a8af23869db72ac05a4336c94233b53..ceff2b4d4245ad9b50d0f1f72d85c769231d108a 100644 (file)
@@ -1,6 +1,8 @@
 <!--
   Copyright © 2016-2018 Soren Stoutner <soren@stoutner.com>.
 
+  Translation 2019 Bernhard G. Keller.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+
   Translation 2016 Aaron Gerlach <aaron@gerlach.com>.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
@@ -29,7 +31,7 @@
         <h3>Websites</h3>
 
         <p><a href="https://www.stoutner.com/category/privacy-browser/"><img class="icon" src="../shared_images/chrome_reader_mode_blue_dark.png"></a>
-            <a href="https://www.stoutner.com/category/privacy-browser/">News</a></p>
+            <a href="https://www.stoutner.com/category/privacy-browser/">Neuigkeiten</a></p>
 
         <p><a href="https://www.stoutner.com/category/roadmap/"><img class="icon" src="../shared_images/map_blue_dark.png"></a>
             <a href="https://www.stoutner.com/category/roadmap/">Roadmap</a></p>
index 256e94f5967000a8b5c1b494c469f23807e592b0..eb21ce419253154f5d32a4b2012ac2b1fdff7406 100644 (file)
@@ -1,6 +1,8 @@
 <!--
   Copyright © 2016-2018 Soren Stoutner <soren@stoutner.com>.
 
+  Translation 2019 Bernhard G. Keller.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+
   Translation 2016 Aaron Gerlach <aaron@gerlach.com>.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
@@ -29,7 +31,7 @@
         <h3>Websites</h3>
 
         <p><a href="https://www.stoutner.com/category/privacy-browser/"><img class="icon" src="../shared_images/chrome_reader_mode_blue_light.png"></a>
-            <a href="https://www.stoutner.com/category/privacy-browser/">News</a></p>
+            <a href="https://www.stoutner.com/category/privacy-browser/">Neuigkeiten</a></p>
 
         <p><a href="https://www.stoutner.com/category/roadmap/"><img class="icon" src="../shared_images/map_blue_light.png"></a>
             <a href="https://www.stoutner.com/category/roadmap/">Roadmap</a></p>
index f3879a8a7796a6433a3144aac200068111de6876..dbda03a05ad42c40807c21c723081cc4d69bd615 100644 (file)
@@ -1,6 +1,8 @@
 <!--
   Copyright © 2016-2018 Soren Stoutner <soren@stoutner.com>.
 
+  Translation 2019 Bernhard G. Keller.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+
   Translation 2018 Stefan Erhardt.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
 
   Translation 2016 Aaron Gerlach <aaron@gerlach.com>.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
         <p><a href="https://developer.android.com/reference/android/Manifest.permission.html#INSTALL_SHORTCUT">com.android.launcher.permission.INSTALL_SHORTCUT</a></p>
         <p>Benötigt, um Verknüpfungen zu Websites auf Ihrer Startseite zu erstellen.</p>
 
-        <h3>Read storage</h3>
+        <h3>Speicher lesen</h3>
         <p><a href="https://developer.android.com/reference/android/Manifest.permission#READ_EXTERNAL_STORAGE">android.permission.READ_EXTERNAL_STORAGE</a></p>
-        <p>Required to import settings from public folders. On Android Marshmallow (API 23) and newer, if this permission is denied Privacy Browser can import settings from the app’s folders instead.</p>
+        <p>Benötigt, um Einstellungen aus öffentlichen Ordnern zu importieren. Unter Android Marshmallow (API 23) und neuer kann Privacy Browser Einstellungen nur aus seinem eigenen Ordner importieren, wenn diese Berechtigung nicht erteilt wird.</p>
 
-        <h3>Write storage</h3>
+        <h3>Speicher schreiben</h3>
         <p><a href="https://developer.android.com/reference/android/Manifest.permission.html#WRITE_EXTERNAL_STORAGE">android.permission.WRITE_EXTERNAL_STORAGE</a></p>
-        <p>Required to export settings and download files to the public folders.
-            On Android Marshmallow (API 23) and newer, if this permission is denied Privacy Browser can export settings and store downloads in the app’s folders instead.</p>
+        <p>Benötigt, um Einstellungen und Downloads in öffentlichen Ordnern zu speichern.
+            Unter Android Marshmallow (API 23) und neuer kann Privacy Browser Einstellungen und Downloads nur in seinem eigenen Ordner speichern, wenn diese Berechtigung nicht erteilt wird.</p>
     </body>
 </html>
\ No newline at end of file
index d14cb9eaf9e08721ec56901978f6b52d73085929..ecaf7b57832b07694313653a5e65f34a57dd290f 100644 (file)
@@ -1,6 +1,8 @@
 <!--
   Copyright © 2016-2018 Soren Stoutner <soren@stoutner.com>.
 
+  Translation 2019 Bernhard G. Keller.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+
   Translation 2018 Stefan Erhardt.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
 
   Translation 2016 Aaron Gerlach <aaron@gerlach.com>.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
         <p><a href="https://developer.android.com/reference/android/Manifest.permission.html#INSTALL_SHORTCUT">com.android.launcher.permission.INSTALL_SHORTCUT</a></p>
         <p>Benötigt, um Verknüpfungen zu Websites auf Ihrer Startseite zu erstellen.</p>
 
-        <h3>Read storage</h3>
+        <h3>Speicher lesen</h3>
         <p><a href="https://developer.android.com/reference/android/Manifest.permission#READ_EXTERNAL_STORAGE">android.permission.READ_EXTERNAL_STORAGE</a></p>
-        <p>Required to import settings from public folders. On Android Marshmallow (API 23) and newer, if this permission is denied Privacy Browser can import settings from the app’s folders instead.</p>
+        <p>Benötigt, um Einstellungen aus öffentlichen Ordnern zu importieren. Unter Android Marshmallow (API 23) und neuer kann Privacy Browser Einstellungen nur aus seinem eigenen Ordner importieren, wenn diese Berechtigung nicht erteilt wird.</p>
 
-        <h3>Write storage</h3>
+        <h3>Speicher schreiben</h3>
         <p><a href="https://developer.android.com/reference/android/Manifest.permission.html#WRITE_EXTERNAL_STORAGE">android.permission.WRITE_EXTERNAL_STORAGE</a></p>
-        <p>Required to export settings and download files to the public folders.
-            On Android Marshmallow (API 23) and newer, if this permission is denied Privacy Browser can export settings and store downloads in the app’s folders instead.</p>
+        <p>Benötigt, um Einstellungen und Downloads in öffentlichen Ordnern zu speichern.
+            Unter Android Marshmallow (API 23) und neuer kann Privacy Browser Einstellungen und Downloads nur in seinem eigenen Ordner speichern, wenn diese Berechtigung nicht erteilt wird.</p>
     </body>
 </html>
\ No newline at end of file
index ef2d325f4db807aba765baf577f74d2f9a882cc9..62a05e73e248db280ccdef83abb9cc3caa96a284 100644 (file)
@@ -1,6 +1,8 @@
 <!--
   Copyright © 2016-2018 Soren Stoutner <soren@stoutner.com>.
 
+  Translation 2019 Bernhard G. Keller.  Copyright assigned to 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
 
     <body>
         <h3>Privacy Browser</h3>
-        <p><strong class="red">Privacy Browser does not collect any user information.</strong></p>
+        <p><strong class="red">Privacy Browser sammelt keinerlei Benutzer-Informationen.</strong></p>
 
 
         <h3>Google Play</h3>
-        <p>Google Play has its <a href="https://www.google.com/intl/en/policies/privacy/">own privacy policy</a>.
-            Google provides <em>anonymized summary installation information</em> to developers, including the number of installs organized by the following categories.</p>
+        <p>Google Play hat seine eigene <a href="https://policies.google.com/privacy?hl=de">Datenschutzerklärung</a>.
+                       Google stellt Entwicklern <em>anonymisierte Installations-Informationen</em> inklusive der Anzahl der Installationen unterteilt in die folgenden Kategorien bereit:</p>
         <ul>
-            <li><item>Android version</item> (eg. Android 7.1)</li>
-            <li><item>Device</item> (eg. Samsung Galaxy S6 [zeroflte])</li>
-            <li><item>Tablets</item> (eg. Tablets 10" and above)</li>
-            <li><item>Country</item> (eg. United States)</li>
-            <li><item>Language</item> (eg. English [United States])</li>
-            <li><item>App version</item> (eg. 14)</li>
-            <li><item>Carrier</item> (eg. T-Mobile - US)</li>
+            <li><item>Android-Version</item> (z.B. Android 7.1)</li>
+            <li><item>Gerät</item> (z.B. Samsung Galaxy S6 [zeroflte])</li>
+            <li><item>Tablets</item> (z.B. 10"-Tablet oder höher)</li>
+            <li><item>Land</item> (z.B. USA)</li>
+            <li><item>Sprache</item> (z.B. Englisch [USA])</li>
+            <li><item>App-Version</item> (z.B. 14)</li>
+            <li><item>Mobilfunk-Anbieter</item> (z.B. T-Mobile - US)</li>
         </ul>
 
 
-        <h3>Google Play Ratings</h3>
-        <p>Google Play has its <a href="https://www.google.com/intl/en/policies/privacy/">own privacy policy</a>.
-            Google provides developers with <em>anonymized summaries</em> of the following information related to user ratings.</p>
+        <h3>Google Play Bewertungen</h3>
+        <p>Google Play hat seine eigene <a href="https://policies.google.com/privacy?hl=de">Datenschutzerklärung</a>.
+                       Google stellt Entwicklern folgende <em>anonymisierte Installations-Informationen</em> bezüglich der Bewertungen bereit:</p>
         <ul>
-            <li><item>Country</item> (eg. United States)</li>
-            <li><item>Language</item> (eg. English)</li>
-            <li><item>App version</item> (eg. 14)</li>
-            <li><item>Android version</item> (eg. Android 7.1)</li>
-            <li><item>Device</item> (eg. Google Nexus 5X [bullhead])</li>
-            <li><item>Tablets</item> (eg. Tablets 10" and above)</li>
+            <li><item>Land</item> (z.B. USA)</li>
+            <li><item>Sprache</item> (z.B. Englisch)</li>
+            <li><item>App-Version</item> (z.B. 14)</li>
+            <li><item>Android-Version</item> (z.B. Android 7.1)</li>
+            <li><item>Gerät</item> (z.B. Google Nexus 5X [bullhead])</li>
+            <li><item>Tablets</item> (z.B. 10"-Tablet oder höher)</li>
         </ul>
 
 
-        <h3>Google Play Reviews</h3>
-        <p>Google Play has its <a href="https://www.google.com/intl/en/policies/privacy/">own privacy policy</a>.
-            In addition to the name of the reviewer, the rating, and the text of the review (which are all available publicly), Google provides some or all of the following information to the developer.</p>
+        <h3>Google Play Rezensionen</h3>
+        <p>Google Play hat seine eigene <a href="https://policies.google.com/privacy?hl=de">Datenschutzerklärung</a>.
+                       Zusätzlich zum Namen des Rezensenten, der Bewertung und dem Text der Rezension (welche alle öffentlich zugänglich sind), stellt Google dem Entwickler einige oder alle der folgenden Informationen zur Verfügung:</p>
         <ul>
-            <li><item>Version code</item> (eg. 7)</li>
-            <li><item>Version name</item> (eg. 1.6)</li>
-            <li><item>Android version</item> (eg. Android 5.1)</li>
-            <li><item>Device</item> (eg. Galaxy S6 Edge+ [zenlte])</li>
-            <li><item>Manufacturer</item> (eg. Samsung)</li>
-            <li><item>Device type</item> (eg. Phone)</li>
-            <li><item>CPU make</item> (eg. Samsung)</li>
-            <li><item>CPU model</item> (eg. Exynos 7420)</li>
-            <li><item>Screen density</item> (eg. 560 dpi)</li>
-            <li><item>Screen size</item> (eg. 2560 x 1440)</li>
-            <li><item>RAM</item> (eg. 4096 MB)</li>
-            <li><item>Native platform</item> (eg. armeabi-v7a,armeabi,arm64v8a)</li>
-            <li><item>OpenGL ES version</item> (eg. 3.1)</li>
-            <li><item>Device language</item> (eg. English)</li>
+            <li><item>Versions-Code</item> (z.B. 7)</li>
+            <li><item>Versions-Name</item> (z.B. 1.6)</li>
+            <li><item>Android-Version</item> (z.B. Android 5.1)</li>
+            <li><item>Gerät</item> (z.B. Galaxy S6 Edge+ [zenlte])</li>
+            <li><item>Hersteller</item> (z.B. Samsung)</li>
+            <li><item>Gerätetyp</item> (z.B. Mobiltelefon)</li>
+            <li><item>CPU-Fabrikat</item> (z.B. Samsung)</li>
+            <li><item>CPU-Model</item> (z.B. Exynos 7420)</li>
+            <li><item>Bildschirm-Auflösung</item> (z.B. 560 dpi)</li>
+            <li><item>Bildschirm-Grösse</item> (z.B. 2560 x 1440)</li>
+            <li><item>RAM</item> (z.B. 4096 MB)</li>
+            <li><item>Native Plattform</item> (z.B. armeabi-v7a,armeabi,arm64v8a)</li>
+            <li><item>OpenGL ES Version</item> (z.B. 3.1)</li>
+            <li><item>Geräte-Sprache</item> (z.B. Englisch)</li>
         </ul>
 
 
-        <h3>Direct Communications</h3>
-        <p>Users may choose to send direct communications to Stoutner, like email messages and comments on <a href="https://www.stoutner.com/">stoutner.com</a>.</p>
+        <h3>Direkte Kommunication</h3>
+        <p>Benutzer können z.B. per Email und Kommentaren auf <a href="https://www.stoutner.com/">stoutner.com</a> direkt mit Stoutner Kontakt aufnehmen.</p>
 
-        <h3>Use of Information</h3>
-        <p><strong class="blue">Stoutner may use this information to assist in the development of Privacy Browser and communicate the status of the project to users.</strong>
-            <strong class="red">Stoutner will never sell this information nor transfer it to any third party that would use it for advertising or marketing.</strong></p>
+        <h3>Daten-Nutzung</h3>
+        <p><strong class="blue">Stoutner kann diese Informationen nutzen, um die Entwicklung von Privacy Browser zu unterstützen und den Status des Projekts an Benutzer zu melden.</strong>
+            <strong class="red">Stoutner wird die Informationen niemals verkaufen oder an Dritte weitergeben, welche diese für Anzeigen oder Marketing nutzen.</strong></p>
 
         <hr />
         <p style="text-align: center;"><em>Revision 1.6, 22. Mai 2018</em></p>
index 793142744b61274a55f03f9e635ad3330ffea9e7..dccd9447e8cef1b2f328099aff0d20f7d54c0073 100644 (file)
@@ -1,6 +1,8 @@
 <!--
   Copyright © 2016-2018 Soren Stoutner <soren@stoutner.com>.
 
+  Translation 2019 Bernhard G. Keller.  Copyright assigned to 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
 
     <body>
         <h3>Privacy Browser</h3>
-        <p><strong class="red">Privacy Browser does not collect any user information.</strong></p>
+        <p><strong class="red">Privacy Browser sammelt keinerlei Benutzer-Informationen.</strong></p>
 
 
         <h3>Google Play</h3>
-        <p>Google Play has its <a href="https://www.google.com/intl/en/policies/privacy/">own privacy policy</a>.
-            Google provides <em>anonymized summary installation information</em> to developers, including the number of installs organized by the following categories.</p>
+        <p>Google Play hat seine eigene <a href="https://policies.google.com/privacy?hl=de">Datenschutzerklärung</a>.
+            Google stellt Entwicklern <em>anonymisierte Installations-Informationen</em> inklusive der Anzahl der Installationen unterteilt in die folgenden Kategorien bereit:</p>
         <ul>
-            <li><item>Android version</item> (eg. Android 7.1)</li>
-            <li><item>Device</item> (eg. Samsung Galaxy S6 [zeroflte])</li>
-            <li><item>Tablets</item> (eg. Tablets 10" and above)</li>
-            <li><item>Country</item> (eg. United States)</li>
-            <li><item>Language</item> (eg. English [United States])</li>
-            <li><item>App version</item> (eg. 14)</li>
-            <li><item>Carrier</item> (eg. T-Mobile - US)</li>
+            <li><item>Android-Version</item> (z.B. Android 7.1)</li>
+            <li><item>Gerät</item> (z.B. Samsung Galaxy S6 [zeroflte])</li>
+            <li><item>Tablets</item> (z.B. 10"-Tablet oder höher)</li>
+            <li><item>Land</item> (z.B. USA)</li>
+            <li><item>Sprache</item> (z.B. Englisch [USA])</li>
+            <li><item>App-Version</item> (z.B. 14)</li>
+            <li><item>Mobilfunk-Anbieter</item> (z.B. T-Mobile - US)</li>
         </ul>
 
 
-        <h3>Google Play Ratings</h3>
-        <p>Google Play has its <a href="https://www.google.com/intl/en/policies/privacy/">own privacy policy</a>.
-            Google provides developers with <em>anonymized summaries</em> of the following information related to user ratings.</p>
+        <h3>Google Play Bewertungen</h3>
+        <p>Google Play hat seine eigene <a href="https://policies.google.com/privacy?hl=de">Datenschutzerklärung</a>.
+            Google stellt Entwicklern folgende <em>anonymisierte Installations-Informationen</em> bezüglich der Bewertungen bereit:</p>
         <ul>
-            <li><item>Country</item> (eg. United States)</li>
-            <li><item>Language</item> (eg. English)</li>
-            <li><item>App version</item> (eg. 14)</li>
-            <li><item>Android version</item> (eg. Android 7.1)</li>
-            <li><item>Device</item> (eg. Google Nexus 5X [bullhead])</li>
-            <li><item>Tablets</item> (eg. Tablets 10" and above)</li>
+            <li><item>Land</item> (z.B. USA)</li>
+            <li><item>Sprache</item> (z.B. Englisch)</li>
+            <li><item>App-Version</item> (z.B. 14)</li>
+            <li><item>Android-Version</item> (z.B. Android 7.1)</li>
+            <li><item>Gerät</item> (z.B. Google Nexus 5X [bullhead])</li>
+            <li><item>Tablets</item> (z.B. 10"-Tablet oder höher)</li>
         </ul>
 
 
-        <h3>Google Play Reviews</h3>
-        <p>Google Play has its <a href="https://www.google.com/intl/en/policies/privacy/">own privacy policy</a>.
-            In addition to the name of the reviewer, the rating, and the text of the review (which are all available publicly), Google provides some or all of the following information to the developer.</p>
+        <h3>Google Play Rezensionen</h3>
+        <p>Google Play hat seine eigene <a href="https://policies.google.com/privacy?hl=de">Datenschutzerklärung</a>.
+            Zusätzlich zum Namen des Rezensenten, der Bewertung und dem Text der Rezension (welche alle öffentlich zugänglich sind), stellt Google dem Entwickler einige oder alle der folgenden Informationen zur Verfügung:</p>
         <ul>
-            <li><item>Version code</item> (eg. 7)</li>
-            <li><item>Version name</item> (eg. 1.6)</li>
-            <li><item>Android version</item> (eg. Android 5.1)</li>
-            <li><item>Device</item> (eg. Galaxy S6 Edge+ [zenlte])</li>
-            <li><item>Manufacturer</item> (eg. Samsung)</li>
-            <li><item>Device type</item> (eg. Phone)</li>
-            <li><item>CPU make</item> (eg. Samsung)</li>
-            <li><item>CPU model</item> (eg. Exynos 7420)</li>
-            <li><item>Screen density</item> (eg. 560 dpi)</li>
-            <li><item>Screen size</item> (eg. 2560 x 1440)</li>
-            <li><item>RAM</item> (eg. 4096 MB)</li>
-            <li><item>Native platform</item> (eg. armeabi-v7a,armeabi,arm64v8a)</li>
-            <li><item>OpenGL ES version</item> (eg. 3.1)</li>
-            <li><item>Device language</item> (eg. English)</li>
+            <li><item>Versions-Code</item> (z.B. 7)</li>
+            <li><item>Versions-Name</item> (z.B. 1.6)</li>
+            <li><item>Android-Version</item> (z.B. Android 5.1)</li>
+            <li><item>Gerät</item> (z.B. Galaxy S6 Edge+ [zenlte])</li>
+            <li><item>Hersteller</item> (z.B. Samsung)</li>
+            <li><item>Gerätetyp</item> (z.B. Mobiltelefon)</li>
+            <li><item>CPU-Fabrikat</item> (z.B. Samsung)</li>
+            <li><item>CPU-Model</item> (z.B. Exynos 7420)</li>
+            <li><item>Bildschirm-Auflösung</item> (z.B. 560 dpi)</li>
+            <li><item>Bildschirm-Grösse</item> (z.B. 2560 x 1440)</li>
+            <li><item>RAM</item> (z.B. 4096 MB)</li>
+            <li><item>Native Plattform</item> (z.B. armeabi-v7a,armeabi,arm64v8a)</li>
+            <li><item>OpenGL ES Version</item> (z.B. 3.1)</li>
+            <li><item>Geräte-Sprache</item> (z.B. Englisch)</li>
         </ul>
 
 
-        <h3>Direct Communications</h3>
-        <p>Users may choose to send direct communications to Stoutner, like email messages and comments on <a href="https://www.stoutner.com/">stoutner.com</a>.</p>
+        <h3>Direkte Kommunication</h3>
+        <p>Benutzer können z.B. per Email und Kommentaren auf <a href="https://www.stoutner.com/">stoutner.com</a> direkt mit Stoutner Kontakt aufnehmen.</p>
 
-        <h3>Use of Information</h3>
-        <p><strong class="blue">Stoutner may use this information to assist in the development of Privacy Browser and communicate the status of the project to users.</strong>
-            <strong class="red">Stoutner will never sell this information nor transfer it to any third party that would use it for advertising or marketing.</strong></p>
+        <h3>Daten-Nutzung</h3>
+        <p><strong class="blue">Stoutner kann diese Informationen nutzen, um die Entwicklung von Privacy Browser zu unterstützen und den Status des Projekts an Benutzer zu melden.</strong>
+            <strong class="red">Stoutner wird die Informationen niemals verkaufen oder an Dritte weitergeben, welche diese für Anzeigen oder Marketing nutzen.</strong></p>
 
         <hr />
         <p style="text-align: center;"><em>Revision 1.6, 22. Mai 2018</em></p>
index a5566ba760912490ec36450e4a383813a65cf420..5c6bb3186d3693d5ac22763a4f1bb387a348f909 100644 (file)
@@ -1,6 +1,8 @@
 <!--
   Copyright © 2018-2019 Soren Stoutner <soren@stoutner.com>.
 
+  Translation 2019 Bernhard G. Keller.  Copyright assigned to 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
     <body>
         <h3><img class="title" src="../shared_images/bookmarks_blue_guide_dark.png"> Lesezeichen</h3>
 
-        <p>Bookmarks can be accessed in a drawer layout by swiping from the right of the screen.</p>
+        <p>Lesezeichen können im Privacy Browser durch Wischen vom rechten Rand erreicht werden.</p>
 
         <img class="center21" src="images/bookmarks.png">
 
-        <p>Tapping the top floating action button loads the bookmarks activity, which has advanced options like moving and deleting bookmarks.
-            From the bookmarks activity, there is an option to load the bookmarks database view.
-            This shows the bookmarks as they exist in the SQLite database, which can be useful for troubleshooting problems with importing and exporting bookmarks.</p>
+        <p>Tippen Sie eines der Icons an, um Lesezeichen in der Lesezeichen-Übersicht zu ändern, umszuortieren oder zu löschen.
+                       In der Lesezeichen-Übersicht gibt es eine Option, um die Lesezeichen in der Datenbank-Ansicht anzuzeigen.
+                       Diese zeigt die Lesezeichen, wie sie in der SQLite-Datenbank enthalten sind. Dies kann bei Problemen beim Importieren oder Exportieren von Lesezeichen hilfreich sein.</p>
     </body>
 </html>
\ No newline at end of file
index dabbf0e07179447f95cd5a3ec9670528b8a5ff9f..f54f21ce3c0131930df4c0400cbd995d066fbc1f 100644 (file)
@@ -1,6 +1,8 @@
 <!--
   Copyright © 2018-2019 Soren Stoutner <soren@stoutner.com>.
 
+  Translation 2019 Bernhard G. Keller.  Copyright assigned to 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
     <body>
         <h3><img class="title" src="../shared_images/bookmarks_blue_light.png"> Lesezeichen</h3>
 
-        <p>Bookmarks can be accessed in a drawer layout by swiping from the right of the screen.</p>
+        <p>Lesezeichen können im Privacy Browser durch Wischen vom rechten Rand erreicht werden.</p>
 
         <img class="center21" src="images/bookmarks.png">
 
-        <p>Tapping the top floating action button loads the bookmarks activity, which has advanced options like moving and deleting bookmarks.
-            From the bookmarks activity, there is an option to load the bookmarks database view.
-            This shows the bookmarks as they exist in the SQLite database, which can be useful for troubleshooting problems with importing and exporting bookmarks.</p>
+        <p>Tippen Sie eines der Icons an, um Lesezeichen in der Lesezeichen-Übersicht zu ändern, umszuortieren oder zu löschen.
+            In der Lesezeichen-Übersicht gibt es eine Option, um die Lesezeichen in der Datenbank-Ansicht anzuzeigen.
+            Diese zeigt die Lesezeichen, wie sie in der SQLite-Datenbank enthalten sind. Dies kann bei Problemen beim Importieren oder Exportieren von Lesezeichen hilfreich sein.</p>
     </body>
 </html>
\ No newline at end of file
index 4a5a87d298b6c7e4f06b4451273bb38216f84f38..3eb483c54e0c19323756dddcd64a1ed9ffe3a144 100644 (file)
@@ -1,6 +1,8 @@
 <!--
   Copyright © 2017-2019 Soren Stoutner <soren@stoutner.com>.
 
+  Translation 2019 Bernhard G. Keller.  Copyright assigned to 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
     </head>
 
     <body>
-        <h3><img class="title" src="../shared_images/dns_blue_dark.png"> Secure Web Browsing</h3>
+        <h3><img class="title" src="../shared_images/dns_blue_dark.png"> Sicheres Internet-Surfen</h3>
 
-        <p>Privacy Browser’s default is to browse with JavaScript, cookies, and DOM storage disabled.
-            However, some websites legitimately need these features enabled to function correctly.
-            Domain settings can automatically turn on a specified set of features when visiting a designated domain.</p>
+        <p>Privacy Browser’s Vorgabe ist das Surfen ohne JavaScript, Cookies und DOM-Speicher.
+                       Allerdings gibt es Websites, die nur korrekt funktionieren, wenn eine oder mehrere dieser Funktionen aktiviert sind.
+                       Sie können daher eine oder mehrere dieser Funktionen automatisch aktivieren, wenn Sie eine solche Seite (Domain) besuchen.</p>
 
         <p><img class="center21" src="images/domain_settings.png"></p>
 
-        <p>When visiting a domain that has domain settings specified, the background of the URL text box is green.</p>
+        <p>Wenn Sie ein Seite besuchen, für die zuvor bereits entsprechende Einstellungen getätigt wurden, wird der Hintergrund der URL-Textbox grün dargestellt.</p>
 
         <p><img class="center21" src="../en/images/green_url_bar.png"></p>
     </body>
index ab4387a7376c20b8e8fc5f696e6eed749c2341ca..b763fe80ed028a82070d324ada5f1b1d385605dd 100644 (file)
@@ -1,6 +1,8 @@
 <!--
   Copyright © 2017-2019 Soren Stoutner <soren@stoutner.com>.
 
+  Translation 2019 Bernhard G. Keller.  Copyright assigned to 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
     </head>
 
     <body>
-        <h3><img class="title" src="../shared_images/dns_blue_light.png"> Secure Web Browsing</h3>
+        <h3><img class="title" src="../shared_images/dns_blue_light.png"> Sicheres Internet-Surfen</h3>
 
-        <p>Privacy Browser’s default is to browse with JavaScript, cookies, and DOM storage disabled.
-            However, some websites legitimately need these features enabled to function correctly.
-            Domain settings can automatically turn on a specified set of features when visiting a designated domain.</p>
+        <p>Privacy Browser’s Vorgabe ist das Surfen ohne JavaScript, Cookies und DOM-Speicher.
+            Allerdings gibt es Websites, die nur korrekt funktionieren, wenn eine oder mehrere dieser Funktionen aktiviert sind.
+            Sie können daher eine oder mehrere dieser Funktionen automatisch aktivieren, wenn Sie eine solche Seite (Domain) besuchen.</p>
 
         <p><img class="center21" src="images/domain_settings.png"></p>
 
-        <p>When visiting a domain that has domain settings specified, the background of the URL text box is green.</p>
+        <p>Wenn Sie ein Seite besuchen, für die zuvor bereits entsprechende Einstellungen getätigt wurden, wird der Hintergrund der URL-Textbox grün dargestellt.</p>
 
         <p><img class="center21" src="../en/images/green_url_bar.png"></p>
     </body>
index fcd4d7cc257d201d5f531d56c02e1814a148617c..58a258dc96e3603cfe6b199924f1468836b0f77c 100644 (file)
@@ -1,6 +1,8 @@
 <!--
   Copyright © 2016-2018 Soren Stoutner <soren@stoutner.com>.
 
+  Translation 2019 Bernhard G. Keller.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+
   Translation 2016 Aaron Gerlach <aaron@gerlach.com>.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
     </head>
 
     <body>
-        <h3><img class="title" src="../shared_images/privacy_browser.png"> JavaScript ist machtvoll</h3>
+        <h3><img class="title" src="../shared_images/privacy_browser.png"> JavaScript ist mächtig</h3>
 
-        <p>In den frühen Tagen des Internets waren Websites statisch, zeigten also nur Text und Bilder, welche auf dem Bildschirm sichtbar sind, veränderten sich aber nicht oder interagierten.
+        <p>In den frühen Tagen des Internets waren Websites statisch, zeigten also nur Text und Bilder, welche auf dem Bildschirm sichtbar sind, veränderten sich  oder interagierten aber nicht.
             Natürlich passierte auf solch statischen Websites nicht viel Interessantes. Viele verschiedene Technologien wurden für dynamische Websites entwickelt. Javascript war eine davon.</p>
 
-        <p>Javascript ist eine Programmiersprache. Viele Webserver hosten Programme geschrieben in Javascript, welche an die Geräte als Teil der Website gesandt werden.
-            Das Gerät führt das Javascript auf dem lokalen Prozessor aus und folgt den Anweisungen des Programms, was beispielsweise Bilder animieren kann, ein Menü öffnen und viele andere großartige Dinge.</p>
+        <p>Javascript ist eine Programmiersprache. Viele Webserver hosten in Javascript geschriebene Programme, welche als Teil der Website an die abrufenden Geräte gesandt werden.
+            Das Gerät führt die Skripts dann auf dem lokalen Gerät aus und folgt den Anweisungen des Programms, um beispielsweise Bilder zu animieren, Menüs zu öffnen und andere derartige Dinge.</p>
 
         <h3><img class="title" src="../shared_images/javascript_enabled.png"> JavaScript ist gefährlich</h3>
 
-        <p>Natürlich birgt das Konzept von willkürlich ausgeführten Programmen aus einer Website heraus ein großes gefahrenprotenzial.
-            Also werden Limitierungen in Javascript gesetzt, damit Dinge wie Viren vorgebeugt wird. Wie auch immer, im Endeffekt sind diese Limitationen sehr ausgedehnt.
-            Unten ist ein Screenshot von <a href="http://webkay.robinlinus.com">webkay</a>,
+        <p>Natürlich birgt das Konzept von willkürlich ausgeführten Programmen aus einer Website heraus auch ein großes Gefahrenprotenzial.
+            Also werden Limitierungen in Javascript gesetzt, die etwa Viren verhindern sollen, jedoch nicht immer wirksam sind.
+            Das Bild unten zeigt einen Screenshot von <a href="http://webkay.robinlinus.com">webkay</a>,
             einer Website die Beispiele für von einem Gerät produzierten Informationen bietet, wenn Javascript auf einem Gerät läuft.
             <a href="http://www.browserleaks.com/">Browser Leaks</a> ist eine andere gute Quelle.</p>
 
         <p><img class="center" src="../en/images/webkay.png"></p>
 
-        <p>Zum Schutz der Privatsphäre wäre es ideal das Web ohne Javascript zu nutzen.  Jedoch benötigen manche Websites legitimerweise
+        <p>Zum Schutz der Privatsphäre wäre es ideal, das Web ohne Javascript zu nutzen.  Jedoch benötigen manche Websites legitimerweise
             JavaScript, um ihre Zwecke zu erfüllen und andere funktionieren nicht korrekt ohne Javascript, selbst wenn sie dazu umprogrammiert werden könnten.
             Privacy Browser geht auf diese Problematik ein und macht es einfach, Javascript zu (de-)aktivieren. Ein Knopfdruck auf das Privatsphäre-Schild wechselt zwischen blau
             <img class="inline" src="../shared_images/privacy_browser.png"> oder gelb <img class="inline" src="../shared_images/warning.png"> (beide zeigen an, dass
-            JavaScript aus ist) und rot <img class="inline" src="../shared_images/javascript_enabled.png"> (JavaScript aktiviert) und aktualisieren die Website.
-            Der Vergleich der verschiedenen Informationen, die <a href="http://webkay.robinlinus.com">webkay</a> mit und ohne Javascript sammeln kann ist informativ.</p>
+            JavaScript aus ist) und rot <img class="inline" src="../shared_images/javascript_enabled.png"> (JavaScript aktiviert) und aktualisiert die Website.
+            Der Vergleich der verschiedenen Informationen, die <a href="http://webkay.robinlinus.com">webkay</a> mit und ohne Javascript sammeln kann, ist sehr informativ.</p>
 
-        <p>Browsing the internet with JavaScript disabled, and only enabling it if needed, goes a long way toward protecting privacy.
-            In addition, JavaScript is used to load much of the annoying advertisements and extra cruft that comes along with most modern websites.
-            With it disabled, websites will load faster, consume less network traffic, and use less CPU power, which leads to longer battery life.</p>
+        <p>Im Internet ohne Javascript unterwegs zu sein oder dieses nur bei Bedarf zu aktivieren, ist ein daher wichtiger Schritt, um die Privatsphäre zu schützen.
+                       Darüber hinaus werden von den meisten modernen Websites nervige Werbebanner und und überflüssiger Müll mit Javascript geladen. 
+                       Wird Javascript deaktiviert, laden Websites daher meist wesentlich schneller, brauchen weniger Datenvolumen und CPU-Leistung, was auch die Lebenszeit der Batterien verlängert.</p>
     </body>
 </html>
\ No newline at end of file
index f6027508088855fab29885d1de3cf25b912598b4..b0446a9ab88bba089957cb020db29b0dfa4c105d 100644 (file)
@@ -1,6 +1,8 @@
 <!--
   Copyright © 2016-2018 Soren Stoutner <soren@stoutner.com>.
 
+  Translation 2019 Bernhard G. Keller.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+
   Translation 2016 Aaron Gerlach <aaron@gerlach.com>.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
     </head>
 
     <body>
-        <h3><img class="title" src="../shared_images/privacy_browser.png"> JavaScript ist machtvoll</h3>
+        <h3><img class="title" src="../shared_images/privacy_browser.png"> JavaScript ist mächtig</h3>
 
-        <p>In den frühen Tagen des Internets waren Websites statisch, zeigten also nur Text und Bilder, welche auf dem Bildschirm sichtbar sind, veränderten sich aber nicht oder interagierten.
+        <p>In den frühen Tagen des Internets waren Websites statisch, zeigten also nur Text und Bilder, welche auf dem Bildschirm sichtbar sind, veränderten sich  oder interagierten aber nicht.
             Natürlich passierte auf solch statischen Websites nicht viel Interessantes. Viele verschiedene Technologien wurden für dynamische Websites entwickelt. Javascript war eine davon.</p>
 
-        <p>Javascript ist eine Programmiersprache. Viele Webserver hosten Programme geschrieben in Javascript, welche an die Geräte als Teil der Website gesandt werden.
-            Das Gerät führt das Javascript auf dem lokalen Prozessor aus und folgt den Anweisungen des Programms, was beispielsweise Bilder animieren kann, ein Menü öffnen und viele andere großartige Dinge.</p>
+        <p>Javascript ist eine Programmiersprache. Viele Webserver hosten in Javascript geschriebene Programme, welche als Teil der Website an die abrufenden Geräte gesandt werden.
+            Das Gerät führt die Skripts dann auf dem lokalen Gerät aus und folgt den Anweisungen des Programms, um beispielsweise Bilder zu animieren, Menüs zu öffnen und andere derartige Dinge.</p>
 
         <h3><img class="title" src="../shared_images/javascript_enabled.png"> JavaScript ist gefährlich</h3>
 
-        <p>Natürlich birgt das Konzept von willkürlich ausgeführten Programmen aus einer Website heraus ein großes gefahrenprotenzial.
-            Also werden Limitierungen in Javascript gesetzt, damit Dinge wie Viren vorgebeugt wird. Wie auch immer, im Endeffekt sind diese Limitationen sehr ausgedehnt.
-            Unten ist ein Screenshot von <a href="http://webkay.robinlinus.com">webkay</a>,
+        <p>Natürlich birgt das Konzept von willkürlich ausgeführten Programmen aus einer Website heraus auch ein großes Gefahrenprotenzial.
+            Also werden Limitierungen in Javascript gesetzt, die etwa Viren verhindern sollen, jedoch nicht immer wirksam sind.
+            Das Bild unten zeigt einen Screenshot von <a href="http://webkay.robinlinus.com">webkay</a>,
             einer Website die Beispiele für von einem Gerät produzierten Informationen bietet, wenn Javascript auf einem Gerät läuft.
             <a href="http://www.browserleaks.com/">Browser Leaks</a> ist eine andere gute Quelle.</p>
 
         <p><img class="center" src="../en/images/webkay.png"></p>
 
-        <p>Zum Schutz der Privatsphäre wäre es ideal das Web ohne Javascript zu nutzen.  Jedoch benötigen manche Websites legitimerweise
+        <p>Zum Schutz der Privatsphäre wäre es ideal, das Web ohne Javascript zu nutzen.  Jedoch benötigen manche Websites legitimerweise
             JavaScript, um ihre Zwecke zu erfüllen und andere funktionieren nicht korrekt ohne Javascript, selbst wenn sie dazu umprogrammiert werden könnten.
             Privacy Browser geht auf diese Problematik ein und macht es einfach, Javascript zu (de-)aktivieren. Ein Knopfdruck auf das Privatsphäre-Schild wechselt zwischen blau
             <img class="inline" src="../shared_images/privacy_browser.png"> oder gelb <img class="inline" src="../shared_images/warning.png"> (beide zeigen an, dass
-            JavaScript aus ist) und rot <img class="inline" src="../shared_images/javascript_enabled.png"> (JavaScript aktiviert) und aktualisieren die Website.
-            Der Vergleich der verschiedenen Informationen, die <a href="http://webkay.robinlinus.com">webkay</a> mit und ohne Javascript sammeln kann ist informativ.</p>
+            JavaScript aus ist) und rot <img class="inline" src="../shared_images/javascript_enabled.png"> (JavaScript aktiviert) und aktualisiert die Website.
+            Der Vergleich der verschiedenen Informationen, die <a href="http://webkay.robinlinus.com">webkay</a> mit und ohne Javascript sammeln kann, ist sehr informativ.</p>
 
-        <p>Browsing the internet with JavaScript disabled, and only enabling it if needed, goes a long way toward protecting privacy.
-            In addition, JavaScript is used to load much of the annoying advertisements and extra cruft that comes along with most modern websites.
-            With it disabled, websites will load faster, consume less network traffic, and use less CPU power, which leads to longer battery life.</p>
+        <p>Im Internet ohne Javascript unterwegs zu sein oder dieses nur bei Bedarf zu aktivieren, ist ein daher wichtiger Schritt, um die Privatsphäre zu schützen.
+            Darüber hinaus werden von den meisten modernen Websites nervige Werbebanner und und überflüssiger Müll mit Javascript geladen.
+            Wird Javascript deaktiviert, laden Websites daher meist wesentlich schneller, brauchen weniger Datenvolumen und CPU-Leistung, was auch die Lebenszeit der Batterien verlängert.</p>
     </body>
 </html>
\ No newline at end of file
index 016071720bfa65fed448bad761d52f4e57e563a0..a31c0fca539c8403e92291ad0d42140300215567 100644 (file)
@@ -1,6 +1,8 @@
 <!--
   Copyright © 2016-2018 Soren Stoutner <soren@stoutner.com>.
 
+  Translation 2019 Bernhard G. Keller.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+
   Translation 2016 Aaron Gerlach <aaron@gerlach.com>.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
     <body>
         <h3><img class="title" src="../shared_images/cookie_blue_dark.png"> Erstanbieter-Cookies</h3>
 
-        <p>Cookies können in zwei Typen unterteilt werden. Erstanbieter-Cookies sind Cookies, die von aktuell besuchten Website gesetzt werden.</p>
-
-        <p>From the early days of the internet, it became obvious that it would be advantageous for websites to be able to store information on a computer for future access.
-            For example, a website that displays weather information could ask the user for a zip code, and then store it in a cookie.
-            The next time the user visited the website, weather information would automatically load for that zip code, without the user having to enter it again.</p>
-
-        <p>Like everything else on the web, clever people figured out all types of ways to abuse cookies to do things that users would not approve of if they knew they were happening.
-            For example, a website can set a cookie with a unique serial number on a device.
-            Then, every time a user visits the website on that device, it can be linked to a unique profile the server maintains for that serial number,
-            even if the device connects from different IP addresses.</p>
-
-        <p>Almost all websites with logins require first-party cookies to be enabled for a user to log in.
-            That is how they make sure it is still you as you move from page to page on the site, and is, in my opinion, one of the few legitimate uses for cookies.</p>
+        <p>Cookies können in zwei Typen unterteilt werden: Erstanbieter-Cookies sind Cookies, die von der aktuell besuchten Website gesetzt werden.</p>
+        
+        <p>Mit solchen Erstanbieter-Cookies können Websites z.B. Informationen auf einem Computer speichern, um diese bei späteren Aufrufen derselben Seite wieder zu verwenden.
+                       Zum Beispiel kann eine Internet-Seite, die das Wetter anzeigt, den Benutzer nach seiner Postleitzahl fragen und diese in einem Cookie speichern.
+                       Beim nächsten Besuch der Website wird dem Benutzer dann automatisch das Wetter für diese Postleitzahl/Region angezeigt, ohne dass der Benutzer diese erneut eingeben muss.</p>
+                       
+               <p>Wie viele anderen Dingen im Internet werden solche Cookies jedoch mittlerweile auch für Zwecke missbraucht, die Website-Besucher normalerweise nicht gestatten würden, wenn sie davon wüssten.
+                       Zum Beispiel können Websites eindeutige Kennzeichen oder Seriennummern in Cookies speichern.
+                       Jedes Mal wenn der Benutzer dann mit seinem Gerät diese Website aufruft, können dessen Bewegungen mit dem Profil dieser Seriennummer verknüpft werden,
+            auch wenn dem Gerät inzwischen eine andere IP-Adresse zugewiesen wurde. So entstehen umfangreiche Profile über die Vorlieben von Website-Besuchern.</p>
 
-        <p>Wenn Erstanbieter-Cookies aktiviert sind aber Javascript deaktiviert, ist das Privatsphäre-Icon gelb <img class="inline" src="../shared_images/warning.png"> als Warnung.</p>
+               <p>Nahezu alle Webseiten, bei denen eine Anmeldung notwendig ist, nutzen Erstanbieter-Cookies für den Anmeldevorgang. 
+                       Damit wird sichergestellt, dass nur der angemeldete Benutzer Zugang zu den entsprechenden Informationen hat. Dies ist meiner Meinung nach eine der wenigen legitimen Verwendungen von Cookies.</p>
 
+        <p>Wenn Erstanbieter-Cookies aktiviert sind, aber Javascript deaktiviert, ist das Privatsphäre-Icon als Warnung gelb <img class="inline" src="../shared_images/warning.png">.</p>
 
         <h3><img class="title" src="../shared_images/cookie_blue_dark.png"> Drittanbieter-Cookies</h3>
 
-        <p>Drittanbieter-Cookies werden von Teilen einer Website gesetzt, die von einem anderen Server als dem aktuell besuchten.
+        <p>Drittanbieter-Cookies werden von Teilen einer Website gesetzt, die von einem anderen Server als dem aktuell besuchten geladen werden.
             Beispielsweise laden viele Websites Werbungen von einem Drittanbieter-Broker wie Googles <a href="https://www.google.com/adsense/start/">Ad Sense</a>.
-            Jedes Mal wenn die Website lädt, fragt bei dem Werbe-Broker an, dass er Werbung zeigen soll.
-            Der Werbe-Broker analysiert jegliche information, die er über den Nutzer hat, vergleicht mit dem aktuell gezahlten Satz der Werbenden Firmen, die die Werbung platziert haben wollen,
-            und wählt die anzuzeigenden Werbungen aus. Der Bereich der Website, auf dem die Werbung angezeigt wird, wird vom Drittanbieter-Broker statt von der eigentlich besuchten Website geladen.</p>
-
-        <p>Weil die meisten Werbungen im Internet von ein paar wenigen Brokern betrieben werden hat es nicht lange gedauert, bis sie gemerkt haben,
-            dass sie einfach einen Tracking-Cookie auf dem Gerät des Benutzers hinterlassen können um immer bescheid zu wissen, wohin er geht.
-            Jedes Mal, wenn eine Werbung von einem Broker geladen wird, ist es seine erste Aufgabe, das Gerät auf eine einmalige Seriennummer in einem cookie zu überprüfen.
-            Wenn es den hat, sucht er das Profil für diese Seriennummer heraus und merkt sich die neu besuchte Seite.
-            Deshalb kann der Nutzer auf der einen Seite nach einem Produkt suchen, nach dem er normalerweise nicht sucht, wie z. B. Walnüsse,
+            Jedes Mal wenn die Website lädt, fragt diese bei dem Werbe-Broker an, dass er Werbung zeigen soll.
+            Der Werbe-Broker analysiert jegliche information, die er über den Nutzer hat, vergleicht diese mit dem aktuell gezahlten Satz der werbenden Firmen,
+            die die Werbung platziert haben wollen und wählt die anzuzeigenden Werbungen aus.
+            Der Bereich der Website, auf dem die Werbung angezeigt wird, wird vom Drittanbieter-Broker statt von der eigentlich besuchten Website geladen.</p>
+
+        <p>Weil die meisten Werbungen im Internet von ein paar wenigen Brokern betrieben werden, hat es nicht lange gedauert, bis diese gemerkt haben,
+            dass sie einfach ein Tracking-Cookie auf dem Gerät des Benutzers hinterlassen können, um immer Bescheid zu wissen, wo sich dieser - auch über Seiten- und Domain-Grenzen hinweg - im Internet bewegt.
+            Jedes Mal, wenn eine Werbung von einem Broker geladen wird, ist es dessen erste Aufgabe, das Gerät auf eine einmalige Seriennummer in einem cookie zu überprüfen.
+            Wenn es diesen hat, sucht er das Profil für diese Seriennummer heraus und merkt sich die neu besuchte Seite.
+            Deshalb kann der Nutzer auf einer Seite nach einem Produkt suchen, nach dem er normalerweise nicht sucht, wie z. B. Walnüsse,
             und plötzlich auf jeder anderen besuchten Website Werbungen für Walnüsse angezeigt bekommen.</p>
 
         <p>Zusätzlich zu Werbe-Brokern machen Social-Media-Plattformen das Gleiche.
-            Vor ein paar Jahren haben die großen Plattformen wie Facebook und Twitter eine große Auswahl von Websites ausgemacht, auf denen es in ihrem größten Interesse wäre,
-            kleine Social Media-Icons zu platzieren. Das sind nicht nur Bilder.
-            Sie beinhalten <a href="https://developers.facebook.com/docs/plugins/like-button/">eingebettete Codes</a>,
-            welche zurück auf die Social-Media-Plattform verlinken und - neben anderen Dingen - einen Drittanbieter-Cookie auf dem Gerät hinterlassen.
+            Vor ein paar Jahren haben die großen Plattformen wie Facebook und Twitter eine große Auswahl von Websites ausgemacht, auf denen es in ihrem größten Interesse wäre, Social Media-Icons zu platzieren.
+            Das sind nicht nur Bilder, sondern auch <a href="https://developers.facebook.com/docs/plugins/like-button/">eingebettete Codes</a>,
+            welche zurück auf die Social-Media-Plattform verlinken und - neben anderen Dingen - ein Drittanbieter-Cookie auf dem Gerät hinterlassen.
             Diese Cookies werden selbst dann gesetzt, wenn der Benutzer keinen Account bei der Social-Media-Plattform hat.
             Mit der Zeit bauten Firmen wie Facebook (welche ebenfalls einen Werbe-Broker betreiben) eine große Anzahl von detaillierten Profilen über Personen an, die
             <a href="http://www.theverge.com/2016/5/27/11795248/facebook-ad-network-non-users-cookies-plug-ins">niemals einen Account auf ihrer Seite erstellt haben</a>.</p>
 
-        <p>There is no good reason to ever enable third-party cookies. On devices with Android KitKat or older (version <= 4.4.4 or API <= 20), WebView does not
-            <a href="https://developer.android.com/reference/android/webkit/CookieManager.html#setAcceptThirdPartyCookies(android.webkit.WebView, boolean)">differentiate
-            between first-party and third-party cookies</a>. Thus, enabling first-party cookies will also enable third-party cookies.</p>
-
+               <p>Es gibt daher kaum einen Grund, solche Drittanbieter-Cookies zuzulassen. Auf Geräten mit Android KitKat oder älter (version <= 4.4.4 or API <= 20) kann 
+                       Webkit leider nicht zwischen Erstanbieter- und Drittanbieter-Cookies unterscheiden.
+            Daher werden auf diesen Geräten <a href="https://developer.android.com/reference/android/webkit/CookieManager.html#setAcceptThirdPartyCookies(android.webkit.WebView, boolean)">
+            auch Drittanbieter-Cookies erlaubt, wenn Erstanbieter-Cookies zugelassen werden.</a></p>
 
         <h3><img class="title" src="../shared_images/web_blue_dark.png"> DOM-Speicher</h3>
-
-        <p>Document Object Model storage, also known as web storage, is like cookies on steroids.
-            Whereas the maximum combined storage size for all cookies from a single URL is 4 kilobytes,
-            DOM storage can hold <a href="https://en.wikipedia.org/wiki/Web_storage#Storage_size">megabytes per site</a>.
-            Because DOM storage uses JavaScript to read and write data, it cannot be enabled unless JavaScript is also enabled.</p>
-
+        
+        <p>DOM-Speicher (Document Object Model-Speicher) - auch bekannt als "Web Storage" - ist ein andere Art, mit der Websites Informationen speichern können.
+                       Während der gesamte Speicherplatz für Cookies einer Webseite auf maximal 4 Kilobytes begrenzt ist, können im DOM-Speicher
+            <a href="https://en.wikipedia.org/wiki/Web_storage#Storage_size">Megabytes pro Seite</a> gespeichert werden - diese Art der Speicherung ist daher gewissermassen Cookies auf Steroiden.
+                       Da der DOM-Speicher jedoch Javascript benötigt um Daten zu schreiben oder zu lesen, kann er nicht aktiviert werden, solange nicht auch JavaScript aktiviert ist.</p>
 
         <h3><img class="title" src="../shared_images/subtitles_blue_dark.png"> Formulardaten</h3>
 
-        <p>Form data contains information typed into web forms, like user names, addresses, phone numbers, etc., and lists them in a drop-down box on future visits.
-            Unlike the other forms of local storage, form data is not sent to the web server without specific user interaction.
-            Beginning in Android Oreo (8.0), WebView’s form data was replaced by the <a href="https://medium.com/@bherbst/getting-androids-autofill-to-work-for-you-21435debea1">Autofill service</a>.
-            As such, controls for form data no longer appear on newer Android devices.</p>
+               <p>Formulardaten umfassen Informationen, die in Web-Formulare eingetippt werden, wie etwa Namen, Adressen, Telefonnummern, usw.
+            Diese werden in Drop-Down-Listen angezeigt, wenn die betreffenden Seiten später wieder aufgerufen werden.
+                       Anders als die oben genannten Formen lokal gespeicherter Informationen werden Formulardaten dabei nicht ohne bewusste Interaktion des Benutzers
+            (z.B. Abschicken eines Formulars) an einen Webserver geschickt.
+                       Ab Android Oreo (8.0) verwendet WebView den <a href="https://medium.com/@bherbst/getting-androids-autofill-to-work-for-you-21435debea1">Autofill-Service</a>.
+            Daher werden die entsprechenden Wahlmöglichkeiten für Formulardaten bei neueren Android-Geräten nicht mehr angezeigt.</p>
     </body>
 </html>
\ No newline at end of file
index 1cb74c4d5626e189aa911190981a4dcccad83113..a6cd9d5c8d6220872fe09eb390d11db3047bd86c 100644 (file)
@@ -1,6 +1,8 @@
 <!--
   Copyright © 2016-2018 Soren Stoutner <soren@stoutner.com>.
 
+  Translation 2019 Bernhard G. Keller.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+
   Translation 2016 Aaron Gerlach <aaron@gerlach.com>.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
     <body>
         <h3><img class="title" src="../shared_images/cookie_blue_light.png"> Erstanbieter-Cookies</h3>
 
-        <p>Cookies können in zwei Typen unterteilt werden. Erstanbieter-Cookies sind Cookies, die von aktuell besuchten Website gesetzt werden.</p>
-
-        <p>From the early days of the internet, it became obvious that it would be advantageous for websites to be able to store information on a computer for future access.
-            For example, a website that displays weather information could ask the user for a zip code, and then store it in a cookie.
-            The next time the user visited the website, weather information would automatically load for that zip code, without the user having to enter it again.</p>
+        <p>Cookies können in zwei Typen unterteilt werden: Erstanbieter-Cookies sind Cookies, die von der aktuell besuchten Website gesetzt werden.</p>
 
-        <p>Like everything else on the web, clever people figured out all types of ways to abuse cookies to do things that users would not approve of if they knew they were happening.
-            For example, a website can set a cookie with a unique serial number on a device.
-            Then, every time a user visits the website on that device, it can be linked to a unique profile the server maintains for that serial number,
-            even if the device connects from different IP addresses.</p>
+        <p>Mit solchen Erstanbieter-Cookies können Websites z.B. Informationen auf einem Computer speichern, um diese bei späteren Aufrufen derselben Seite wieder zu verwenden.
+            Zum Beispiel kann eine Internet-Seite, die das Wetter anzeigt, den Benutzer nach seiner Postleitzahl fragen und diese in einem Cookie speichern.
+            Beim nächsten Besuch der Website wird dem Benutzer dann automatisch das Wetter für diese Postleitzahl/Region angezeigt, ohne dass der Benutzer diese erneut eingeben muss.</p>
 
-        <p>Almost all websites with logins require first-party cookies to be enabled for a user to log in.
-            That is how they make sure it is still you as you move from page to page on the site, and is, in my opinion, one of the few legitimate uses for cookies.</p>
+        <p>Wie viele anderen Dingen im Internet werden solche Cookies jedoch mittlerweile auch für Zwecke missbraucht, die Website-Besucher normalerweise nicht gestatten würden, wenn sie davon wüssten.
+            Zum Beispiel können Websites eindeutige Kennzeichen oder Seriennummern in Cookies speichern.
+            Jedes Mal wenn der Benutzer dann mit seinem Gerät diese Website aufruft, können dessen Bewegungen mit dem Profil dieser Seriennummer verknüpft werden,
+            auch wenn dem Gerät inzwischen eine andere IP-Adresse zugewiesen wurde. So entstehen umfangreiche Profile über die Vorlieben von Website-Besuchern.</p>
 
-        <p>Wenn Erstanbieter-Cookies aktiviert sind aber Javascript deaktiviert, ist das Privatsphäre-Icon gelb <img class="inline" src="../shared_images/warning.png"> als Warnung.</p>
+        <p>Nahezu alle Webseiten, bei denen eine Anmeldung notwendig ist, nutzen Erstanbieter-Cookies für den Anmeldevorgang.
+            Damit wird sichergestellt, dass nur der angemeldete Benutzer Zugang zu den entsprechenden Informationen hat. Dies ist meiner Meinung nach eine der wenigen legitimen Verwendungen von Cookies.</p>
 
+        <p>Wenn Erstanbieter-Cookies aktiviert sind, aber Javascript deaktiviert, ist das Privatsphäre-Icon als Warnung gelb <img class="inline" src="../shared_images/warning.png">.</p>
 
         <h3><img class="title" src="../shared_images/cookie_blue_light.png"> Drittanbieter-Cookies</h3>
 
-        <p>Drittanbieter-Cookies werden von Teilen einer Website gesetzt, die von einem anderen Server als dem aktuell besuchten.
+        <p>Drittanbieter-Cookies werden von Teilen einer Website gesetzt, die von einem anderen Server als dem aktuell besuchten geladen werden.
             Beispielsweise laden viele Websites Werbungen von einem Drittanbieter-Broker wie Googles <a href="https://www.google.com/adsense/start/">Ad Sense</a>.
-            Jedes Mal wenn die Website lädt, fragt bei dem Werbe-Broker an, dass er Werbung zeigen soll.
-            Der Werbe-Broker analysiert jegliche information, die er über den Nutzer hat, vergleicht mit dem aktuell gezahlten Satz der Werbenden Firmen, die die Werbung platziert haben wollen,
-            und wählt die anzuzeigenden Werbungen aus. Der Bereich der Website, auf dem die Werbung angezeigt wird, wird vom Drittanbieter-Broker statt von der eigentlich besuchten Website geladen.</p>
-
-        <p>Weil die meisten Werbungen im Internet von ein paar wenigen Brokern betrieben werden hat es nicht lange gedauert, bis sie gemerkt haben,
-            dass sie einfach einen Tracking-Cookie auf dem Gerät des Benutzers hinterlassen können um immer bescheid zu wissen, wohin er geht.
-            Jedes Mal, wenn eine Werbung von einem Broker geladen wird, ist es seine erste Aufgabe, das Gerät auf eine einmalige Seriennummer in einem cookie zu überprüfen.
-            Wenn es den hat, sucht er das Profil für diese Seriennummer heraus und merkt sich die neu besuchte Seite.
-            Deshalb kann der Nutzer auf der einen Seite nach einem Produkt suchen, nach dem er normalerweise nicht sucht, wie z. B. Walnüsse,
+            Jedes Mal wenn die Website lädt, fragt diese bei dem Werbe-Broker an, dass er Werbung zeigen soll.
+            Der Werbe-Broker analysiert jegliche information, die er über den Nutzer hat, vergleicht diese mit dem aktuell gezahlten Satz der werbenden Firmen,
+            die die Werbung platziert haben wollen und wählt die anzuzeigenden Werbungen aus.
+            Der Bereich der Website, auf dem die Werbung angezeigt wird, wird vom Drittanbieter-Broker statt von der eigentlich besuchten Website geladen.</p>
+
+        <p>Weil die meisten Werbungen im Internet von ein paar wenigen Brokern betrieben werden, hat es nicht lange gedauert, bis diese gemerkt haben,
+            dass sie einfach ein Tracking-Cookie auf dem Gerät des Benutzers hinterlassen können, um immer Bescheid zu wissen, wo sich dieser - auch über Seiten- und Domain-Grenzen hinweg - im Internet bewegt.
+            Jedes Mal, wenn eine Werbung von einem Broker geladen wird, ist es dessen erste Aufgabe, das Gerät auf eine einmalige Seriennummer in einem cookie zu überprüfen.
+            Wenn es diesen hat, sucht er das Profil für diese Seriennummer heraus und merkt sich die neu besuchte Seite.
+            Deshalb kann der Nutzer auf einer Seite nach einem Produkt suchen, nach dem er normalerweise nicht sucht, wie z. B. Walnüsse,
             und plötzlich auf jeder anderen besuchten Website Werbungen für Walnüsse angezeigt bekommen.</p>
 
         <p>Zusätzlich zu Werbe-Brokern machen Social-Media-Plattformen das Gleiche.
-            Vor ein paar Jahren haben die großen Plattformen wie Facebook und Twitter eine große Auswahl von Websites ausgemacht, auf denen es in ihrem größten Interesse wäre,
-            kleine Social Media-Icons zu platzieren. Das sind nicht nur Bilder.
-            Sie beinhalten <a href="https://developers.facebook.com/docs/plugins/like-button/">eingebettete Codes</a>,
-            welche zurück auf die Social-Media-Plattform verlinken und - neben anderen Dingen - einen Drittanbieter-Cookie auf dem Gerät hinterlassen.
+            Vor ein paar Jahren haben die großen Plattformen wie Facebook und Twitter eine große Auswahl von Websites ausgemacht, auf denen es in ihrem größten Interesse wäre, Social Media-Icons zu platzieren.
+            Das sind nicht nur Bilder, sondern auch <a href="https://developers.facebook.com/docs/plugins/like-button/">eingebettete Codes</a>,
+            welche zurück auf die Social-Media-Plattform verlinken und - neben anderen Dingen - ein Drittanbieter-Cookie auf dem Gerät hinterlassen.
             Diese Cookies werden selbst dann gesetzt, wenn der Benutzer keinen Account bei der Social-Media-Plattform hat.
             Mit der Zeit bauten Firmen wie Facebook (welche ebenfalls einen Werbe-Broker betreiben) eine große Anzahl von detaillierten Profilen über Personen an, die
             <a href="http://www.theverge.com/2016/5/27/11795248/facebook-ad-network-non-users-cookies-plug-ins">niemals einen Account auf ihrer Seite erstellt haben</a>.</p>
 
-        <p>There is no good reason to ever enable third-party cookies. On devices with Android KitKat or older (version <= 4.4.4 or API <= 20), WebView does not
-            <a href="https://developer.android.com/reference/android/webkit/CookieManager.html#setAcceptThirdPartyCookies(android.webkit.WebView, boolean)">differentiate
-            between first-party and third-party cookies</a>. Thus, enabling first-party cookies will also enable third-party cookies.</p>
-
+        <p>Es gibt daher kaum einen Grund, solche Drittanbieter-Cookies zuzulassen. Auf Geräten mit Android KitKat oder älter (version <= 4.4.4 or API <= 20) kann
+            Webkit leider nicht zwischen Erstanbieter- und Drittanbieter-Cookies unterscheiden.
+            Daher werden auf diesen Geräten <a href="https://developer.android.com/reference/android/webkit/CookieManager.html#setAcceptThirdPartyCookies(android.webkit.WebView, boolean)">
+                auch Drittanbieter-Cookies erlaubt, wenn Erstanbieter-Cookies zugelassen werden.</a></p>
 
         <h3><img class="title" src="../shared_images/web_blue_light.png"> DOM-Speicher</h3>
 
-        <p>Document Object Model storage, also known as web storage, is like cookies on steroids.
-            Whereas the maximum combined storage size for all cookies from a single URL is 4 kilobytes,
-            DOM storage can hold <a href="https://en.wikipedia.org/wiki/Web_storage#Storage_size">megabytes per site</a>.
-            Because DOM storage uses JavaScript to read and write data, it cannot be enabled unless JavaScript is also enabled.</p>
-
+        <p>DOM-Speicher (Document Object Model-Speicher) - auch bekannt als "Web Storage" - ist ein andere Art, mit der Websites Informationen speichern können.
+            Während der gesamte Speicherplatz für Cookies einer Webseite auf maximal 4 Kilobytes begrenzt ist, können im DOM-Speicher
+            <a href="https://en.wikipedia.org/wiki/Web_storage#Storage_size">Megabytes pro Seite</a> gespeichert werden - diese Art der Speicherung ist daher gewissermassen Cookies auf Steroiden.
+            Da der DOM-Speicher jedoch Javascript benötigt um Daten zu schreiben oder zu lesen, kann er nicht aktiviert werden, solange nicht auch JavaScript aktiviert ist.</p>
 
         <h3><img class="title" src="../shared_images/subtitles_blue_light.png"> Formulardaten</h3>
 
-        <p>Form data contains information typed into web forms, like user names, addresses, phone numbers, etc., and lists them in a drop-down box on future visits.
-            Unlike the other forms of local storage, form data is not sent to the web server without specific user interaction.
-            Beginning in Android Oreo (8.0), WebView’s form data was replaced by the <a href="https://medium.com/@bherbst/getting-androids-autofill-to-work-for-you-21435debea1">Autofill service</a>.
-            As such, controls for form data no longer appear on newer Android devices.</p>
+        <p>Formulardaten umfassen Informationen, die in Web-Formulare eingetippt werden, wie etwa Namen, Adressen, Telefonnummern, usw.
+            Diese werden in Drop-Down-Listen angezeigt, wenn die betreffenden Seiten später wieder aufgerufen werden.
+            Anders als die oben genannten Formen lokal gespeicherter Informationen werden Formulardaten dabei nicht ohne bewusste Interaktion des Benutzers
+            (z.B. Abschicken eines Formulars) an einen Webserver geschickt.
+            Ab Android Oreo (8.0) verwendet WebView den <a href="https://medium.com/@bherbst/getting-androids-autofill-to-work-for-you-21435debea1">Autofill-Service</a>.
+            Daher werden die entsprechenden Wahlmöglichkeiten für Formulardaten bei neueren Android-Geräten nicht mehr angezeigt.</p>
     </body>
 </html>
\ No newline at end of file
index 10e5f250937b0dece28ed815e0c2f8bc04e7b98b..89aa1f5b82efa5f50fed6fd2d8c0d26f40c5ec20 100644 (file)
@@ -1,6 +1,8 @@
 <!--
   Copyright © 2016-2018 Soren Stoutner <soren@stoutner.com>.
 
+  Translation 2019 Bernhard G. Keller.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+
   Translation 2016 Aaron Gerlach <aaron@gerlach.com>.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
     <body>
         <h3><img class="title" src="../shared_images/visibility_off_blue_dark.png"> Echte Privatsphäre</h3>
 
-        <p>Privacy Browser wurde entwickelt um Ihnen während des Surfens die maximal mögliche Kontrolle über Ihre Privatsphäre zu gewähren.
-            Wenn Sie sich mit einer Website verbinden werden bestimmte Informationen, die zum Verarbeiten der Verbindung nötig sind, an den Server gesandt.
+        <p>Privacy Browser wurde entwickelt, um Ihnen während des Surfens die maximal mögliche Kontrolle über Ihre Privatsphäre zu gewähren.
+            Wenn Sie sich mit einer Website verbinden, werden bestimmte Informationen, die zum Verarbeiten der Verbindung nötig sind, an den Server gesandt.
             Der Server erhält beispielsweise Ihre IP-Adresse und eine Port-Nummer, welche er benötigt, damit er weiß, wohin er antworten soll.
-            Diese Informationen werden häufig gespeichert, damit der Webseitenbetreiber Statistiken generieren kann darüber,
-            wie oft eine Website geladen wird und wie viele verschiedene IP-Adressen sie besucht haben.</p>
+            Diese Informationen werden häufig gespeichert, damit der Webseitenbetreiber Statistiken darüber generieren kann,
+            wie oft eine Website geladen wird und wie viele verschiedene IP-Adressen bzw. Besucher sie besucht haben.</p>
 
-        <p>Darüber hinaus wollen die meisten Webseitenbetreiber Ihr Surfverhalten im ganzen Internet und nicht nur auf einem einzigen Server nachverfolgen.
+        <p>Darüber hinaus wollen viele Webseitenbetreiber das Surfverhalten ihrer Besucher im ganzen Internet und nicht nur auf einem einzigen Server nachverfolgen.
             Sie nutzen dabei verschiedene technische Möglichkeiten. Einige davon nutzen das Anfragen oder Platzieren von bestimmten Informationen auf Ihrem Gerät, die das Nachverfolgen ermöglichen.
             Nahezu alle Browser nehmen freiwillig an dieser Verfolgung Teil, ohne den Nutzer darüber zu informieren.
-            Privacy Browser ist dazu entwickelt, dass der Benutzer so viel Information und Kontrolle über diese Verfolgungstechniken wie möglich erhält.</p>
-
+            Privacy Browser ist dazu entwickelt, dass der Benutzer so viel Information und Kontrolle wie möglich über diese Verfolgungstechniken erhält.</p>
 
         <h3><img class="title" src="../shared_images/chrome_reader_mode_blue_guide_dark.png"> Android's WebView Einschränkungen</h3>
-
-        <p>Privacy Browser uses Android’s built-in WebView to render websites. There are some limitations in the controls WebView exposes for managing privacy settings.
-            For example, it isn’t possible to enable some JavaScript commands while disabling others.
-            In the future, Privacy Browser will switch to a custom WebView called <a href="https://www.stoutner.com/category/roadmap/">Privacy WebView</a>.</p>
+        
+        <p>Privacy Browser nutzt Android's eingebaute WebView-Komponente zum Anzeigen von Websites.
+            Daher gibt es einige Einschränkungen in Bezug auf die von WebView bereitgestellten Kontrollmöglichkeiten der Privatsphäre-Einstellungen.
+                       Zum Beispiel ist es nicht möglich, manche JavaScript-Kommandos zu aktivieren und andere zu deaktivieren.
+                       Für die Zukunft ist geplant, dass Privacy Browser zu einer eigenen WebView-Komponente namens <a href="https://www.stoutner.com/category/roadmap/">Privacy WebView</a> wechselt.</p>
     </body>
 </html>
\ No newline at end of file
index 26e3413830d525c0d9a7f4b0422c02c9be10e4b3..38d4fc5de1534321a1b4ccb69bc96b9bdaf2af84 100644 (file)
@@ -1,6 +1,8 @@
 <!--
   Copyright © 2016-2018 Soren Stoutner <soren@stoutner.com>.
 
+  Translation 2019 Bernhard G. Keller.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+
   Translation 2016 Aaron Gerlach <aaron@gerlach.com>.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
     <body>
         <h3><img class="title" src="../shared_images/visibility_off_blue_light.png"> Echte Privatsphäre</h3>
 
-        <p>Privacy Browser wurde entwickelt um Ihnen während des Surfens die maximal mögliche Kontrolle über Ihre Privatsphäre zu gewähren.
-            Wenn Sie sich mit einer Website verbinden werden bestimmte Informationen, die zum Verarbeiten der Verbindung nötig sind, an den Server gesandt.
+        <p>Privacy Browser wurde entwickelt, um Ihnen während des Surfens die maximal mögliche Kontrolle über Ihre Privatsphäre zu gewähren.
+            Wenn Sie sich mit einer Website verbinden, werden bestimmte Informationen, die zum Verarbeiten der Verbindung nötig sind, an den Server gesandt.
             Der Server erhält beispielsweise Ihre IP-Adresse und eine Port-Nummer, welche er benötigt, damit er weiß, wohin er antworten soll.
-            Diese Informationen werden häufig gespeichert, damit der Webseitenbetreiber Statistiken generieren kann darüber,
-            wie oft eine Website geladen wird und wie viele verschiedene IP-Adressen sie besucht haben.</p>
+            Diese Informationen werden häufig gespeichert, damit der Webseitenbetreiber Statistiken darüber generieren kann,
+            wie oft eine Website geladen wird und wie viele verschiedene IP-Adressen bzw. Besucher sie besucht haben.</p>
 
-        <p>Darüber hinaus wollen die meisten Webseitenbetreiber Ihr Surfverhalten im ganzen Internet und nicht nur auf einem einzigen Server nachverfolgen.
+        <p>Darüber hinaus wollen viele Webseitenbetreiber das Surfverhalten ihrer Besucher im ganzen Internet und nicht nur auf einem einzigen Server nachverfolgen.
             Sie nutzen dabei verschiedene technische Möglichkeiten. Einige davon nutzen das Anfragen oder Platzieren von bestimmten Informationen auf Ihrem Gerät, die das Nachverfolgen ermöglichen.
             Nahezu alle Browser nehmen freiwillig an dieser Verfolgung Teil, ohne den Nutzer darüber zu informieren.
-            Privacy Browser ist dazu entwickelt, dass der Benutzer so viel Information und Kontrolle über diese Verfolgungstechniken wie möglich erhält.</p>
-
+            Privacy Browser ist dazu entwickelt, dass der Benutzer so viel Information und Kontrolle wie möglich über diese Verfolgungstechniken erhält.</p>
 
         <h3><img class="title" src="../shared_images/chrome_reader_mode_blue_light.png"> Android's WebView Einschränkungen</h3>
 
-        <p>Privacy Browser uses Android’s built-in WebView to render websites. There are some limitations in the controls WebView exposes for managing privacy settings.
-            For example, it isn’t possible to enable some JavaScript commands while disabling others.
-            In the future, Privacy Browser will switch to a custom WebView called <a href="https://www.stoutner.com/category/roadmap/">Privacy WebView</a>.</p>
+        <p>Privacy Browser nutzt Android's eingebaute WebView-Komponente zum Anzeigen von Websites.
+            Daher gibt es einige Einschränkungen in Bezug auf die von WebView bereitgestellten Kontrollmöglichkeiten der Privatsphäre-Einstellungen.
+            Zum Beispiel ist es nicht möglich, manche JavaScript-Kommandos zu aktivieren und andere zu deaktivieren.
+            Für die Zukunft ist geplant, dass Privacy Browser zu einer eigenen WebView-Komponente namens <a href="https://www.stoutner.com/category/roadmap/">Privacy WebView</a> wechselt.</p>
     </body>
 </html>
\ No newline at end of file
index 3d19c542e3b1d9341473573b0de3a8b9c7e30eb5..4cb32db026ea7025f77c93d4c06785ce44fc3496 100644 (file)
@@ -1,6 +1,8 @@
 <!--
   Copyright © 2018 Soren Stoutner <soren@stoutner.com>.
 
+  Translation 2019 Bernhard G. Keller.  Copyright assigned to 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
     </head>
 
     <body>
-        <h3><img class="title" src="../shared_images/local_activity_blue_dark.png"> Resource Requests</h3>
-
-        <p>When a URL is loaded, it typically makes a number of resource requests for CCS, JavaScript, image, and other files. Details about these requests can be viewed in the Requests activity.
-            The navigation drawer has a link to the Requests activity and also shows how many requests were blocked. Tapping on a request displays details about why it was allowed or blocked.</p>
+        <h3><img class="title" src="../shared_images/local_activity_blue_dark.png"> Resourcen-Zugriffe</h3>
+        
+        <p>Wenn eine URL geladen wird, wird üblicherweise eine Menge Anfragen für mit der Seite verbundene Ressourcen wie Bilder, CSS-, JavaScript- und andere Dateien an den betreffenden Webserver gestellt.
+                       Details dazu können in der Ansicht "Zugriffe" betrachtet werden, welche über das Navigations-Menü links erreicht werden kann.
+                       In dieser Ansicht wird auch dargestellt, wie viele (und welche) Anfragen geblockt wurden.
+            Durch Antippen der betreffenden Anfragen können weitere Details dazu angezeigt werden, die zeigen, warum die Anfrage erlaubt oder blockiert wurde.</p>
 
         <p><img class="center21" src="images/request_details.png"></p>
 
-        <p>Privacy Browser includes four <a href="https://easylist.to/">common blocklists</a> based on the <a href="https://adblockplus.org/filters">Adblock syntax</a>:
-            EasyList, EasyPrivacy, Fanboy’s Annoyance List, and Fanboy’s Social Blocking List.
-            These blocklists are processed by Privacy Browser into the following 22 sublists, which check resource requests in the order listed.</p>
+               <p>Privacy Browser enthält vier <a href="https://easylist.to/">gebräuchliche Filterlisten</a>, die auf der <a href="https://adblockplus.org/filters">Adblock-Syntax</a> basieren:
+                       EasyList, EasyPrivacy, Fanboy’s Annoyance Filterliste und Fanboy’s Social Blocking Filterliste.
+                       Diese werden in 22 Unter-Listen aufgeteilt, welche die Ressourcen-Zugriffe in der folgenden Reihenfolge überprüfen:</p>
 
         <ol>
-            <li>Main Whitelist</li>
-            <li>Final Whitelist</li>
-            <li>Domain Whitelist</li>
-            <li>Domain Initial Whitelist</li>
-            <li>Domain Final Whitelist</li>
-            <li>Third-Party Whitelist</li>
-            <li>Third-Party Domain Whitelist</li>
-            <li>Third-Party Domain Initial Whitelist</li>
-            <li>Main Blacklist</li>
-            <li>Initial Blacklist</li>
-            <li>Final Blacklist</li>
-            <li>Domain Blacklist</li>
-            <li>Domain Initial Blacklist</li>
-            <li>Domain Final Blacklist</li>
-            <li>Domain Regular Expression Blacklist</li>
-            <li>Third-Party Blacklist</li>
-            <li>Third-Party Initial Blacklist</li>
-            <li>Third-Party Domain Blacklist</li>
-            <li>Third-Party Domain Initial Blacklist</li>
-            <li>Third-Party Regular Expression Blacklist</li>
-            <li>Third-Party Domain Regular Expression Blacklist</li>
-            <li>Regular Expression Blacklist</li>
+            <li>Haupt-Positivliste</li>
+            <li>Positivliste (URL-Ende)</li>
+            <li>Domänen-Positivliste</li>
+            <li>Domänen-Positivliste (URL-Anfang)</li>
+            <li>Domänen-Positivliste (URL-Ende)</li>
+            <li>Drittanbieter-Positivliste</li>
+            <li>Drittanbieter-Domänen-Positivliste</li>
+            <li>Drittanbieter-Domänen-Positivliste (URL-Anfang)</li>
+            <li>Haupt-Negativliste</li>
+            <li>Negativliste (URL-Anfang)</li>
+            <li>Negativliste (URL-Ende)</li>
+            <li>Domänen-Negativliste</li>
+            <li>Domänen-Negativliste (URL-Anfang)</li>
+            <li>Domänen-Negativliste (URL-Ende)</li>
+            <li>Domänen-Negativliste mit regulären Ausdrücken</li>
+            <li>Drittanbieter-Negativliste</li>
+            <li>Drittanbieter-Negativliste (URL-Anfang)</li>
+            <li>Drittanbieter-Domänen-Negativliste</li>
+            <li>Drittanbieter-Domänen-Negativliste (URL-Anfang)</li>
+            <li>Drittanbieter-Negativliste mit regulären Ausdrücken</li>
+            <li>Drittanbieter-Domänen-Negativliste mit regulären Ausdrücken</li>
+            <li>Negativliste mit regulären Ausdrücken</li>
         </ol>
 
-        <p>Initial lists check against the beginning of the URL. Final lists check against the end of the URL. Domain lists only check against certain domains.
-            Third-party lists only apply if the root domain of the request is different than the root domain of the main URL.
-            Regular expression lists follow the <a href="https://en.wikipedia.org/wiki/Regular_expression">regular expression syntax</a>. Each sublist item has one or more entry.
-            In the case of domain sublists, the resource request is only checked against the item if the first entry matches the domain of the main URL.</p>
+        <p>Listen mit "(URL-Anfang)" prüfen gegen den Anfang einer URL, solche mit "(URL-Ende)" gegen das Ende der URL. Domänen-Listen prüfen gegen bestimmte Domains.
+                       Drittanbieter-Listen greifen nur, wenn die Domain der Anfrage eine andere ist als die Domain der aufgerufenen URL.
+                       Listen mit regulären Ausdrücken folgen der <a href="https://de.wikipedia.org/wiki/Reguläre_Ausdrücke">Syntax für reguläre Ausdrücke</a>. Jede Unter-Liste hat dabei einen oder mehrere Einträge.
+                       Bei Domänen-Unterlisten werden die Ressourcen-Zugriffe nur dann überprüft, wenn der erste Eintrag der Domain der aufgerufenen URL entspricht.</p>
 
-        <p>Because of limitations in Android’s WebView, and to speed up processing of requests, Privacy Browser implements a simplified interpretation of the Adblock syntax.
-            This can sometimes lead to false positives, where resources are allowed or blocked in ways that weren’t intended by the original entry.
-            A more detailed description of how the blocklist entries are processed is available at <a href="https://www.stoutner.com/privacy-browser/blocklists/">stoutner.com</a>.</p>
+               <p>Aufgrund von Einschränkungen in Android’s WebView und um die Geschwindigkeit von Anfragen zu steigern, verwendet Privacy Browser eine vereinfachte Auswertung der Adblock-Syntax.
+                       Dies kann manchmal zu "false positives" führen, bei denen Ressourcen erlaubt oder blockiert werden, die in den originalen Einträgen anders intendiert wären.
+                       Eine detailiertere Beschreibung, wie die Listen-Einträge abgearbeitet werden, ist unter <a href="https://www.stoutner.com/privacy-browser/blocklists/">stoutner.com</a> verfügbar.</p>
 
-        <p>Privacy Browser has two additional blocklists,
-            one called <a href="https://www.stoutner.com/privacy-browser/blocklists/ultraprivacy/">UltraPrivacy</a> that blocks trackers that EasyPrivacy allows,
-            and the other that blocks all third-party requests.
-            A request is only considered third-party if the base domain of the request is different than the base domain of the URL.
-            For example, if <code>www.website.com</code> loads a picture from <code>images.website.com</code>,
-            this is not blocked as a third-party request because they both share the same base domain of <code>website.com</code>.
-            Blocking all third-party requests increases privacy, but this blocklist is disabled by default because it breaks a large number of websites.</p>
+               <p>Privacy Browser hat zwei weitere Sperrlisten: <a href="https://www.stoutner.com/privacy-browser/blocklists/ultraprivacy/">UltraPrivacy</a>, welche Tracker blockiert, die EasyPrivacy erlaubt,
+            und eine andere, die alle Drittanbieter-Anfragen blockiert.
+                       Eine Anfrage wird dabei nur als Drittanbieter-Anfrage gewertet, wenn die Basis-Domain der Anfrage eine andere ist als die Basis-Domain der angefragten URL.
+                       Lädt zum Beispiel die Seite <code>www.website.com</code> ein Bild von <code>images.website.com</code>, wird diese Anfrage nicht als Drittanbieter-Anfrage gewertet,
+            da die beiden Anfragen dieselbe Basis-Domain <code>website.com</code> haben.
+                       Alle Drittanbieter-Anfragen zu blockieren verbessert zwar die Privatsphäre, diese Einstellung ist jedoch standardmäßig deaktiviert, da dadurch viele Webseiten verunstaltet werden können.</p>
     </body>
 </html>
\ No newline at end of file
index b7511a464624cb4cda4dd5f0ec9ee0b6a4ab1a08..587f4f01eef31a37fea80b61299f6f0bea1d62e3 100644 (file)
@@ -1,6 +1,8 @@
 <!--
   Copyright © 2018 Soren Stoutner <soren@stoutner.com>.
 
+  Translation 2019 Bernhard G. Keller.  Copyright assigned to 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
     </head>
 
     <body>
-        <h3><img class="title" src="../shared_images/local_activity_blue_light.png"> Resource Requests</h3>
+        <h3><img class="title" src="../shared_images/local_activity_blue_light.png"> Resourcen-Zugriffe</h3>
 
-        <p>When a URL is loaded, it typically makes a number of resource requests for CCS, JavaScript, image, and other files. Details about these requests can be viewed in the Requests activity.
-            The navigation drawer has a link to the Requests activity and also shows how many requests were blocked. Tapping on a request displays details about why it was allowed or blocked.</p>
+        <p>Wenn eine URL geladen wird, wird üblicherweise eine Menge Anfragen für mit der Seite verbundene Ressourcen wie Bilder, CSS-, JavaScript- und andere Dateien an den betreffenden Webserver gestellt.
+            Details dazu können in der Ansicht "Zugriffe" betrachtet werden, welche über das Navigations-Menü links erreicht werden kann.
+            In dieser Ansicht wird auch dargestellt, wie viele (und welche) Anfragen geblockt wurden.
+            Durch Antippen der betreffenden Anfragen können weitere Details dazu angezeigt werden, die zeigen, warum die Anfrage erlaubt oder blockiert wurde.</p>
 
         <p><img class="center21" src="images/request_details.png"></p>
 
-        <p>Privacy Browser includes four <a href="https://easylist.to/">common blocklists</a> based on the <a href="https://adblockplus.org/filters">Adblock syntax</a>:
-            EasyList, EasyPrivacy, Fanboy’s Annoyance List, and Fanboy’s Social Blocking List.
-            These blocklists are processed by Privacy Browser into the following 22 sublists, which check resource requests in the order listed.</p>
+        <p>Privacy Browser enthält vier <a href="https://easylist.to/">gebräuchliche Filterlisten</a>, die auf der <a href="https://adblockplus.org/filters">Adblock-Syntax</a> basieren:
+            EasyList, EasyPrivacy, Fanboy’s Annoyance Filterliste und Fanboy’s Social Blocking Filterliste.
+            Diese werden in 22 Unter-Listen aufgeteilt, welche die Ressourcen-Zugriffe in der folgenden Reihenfolge überprüfen:</p>
 
         <ol>
-            <li>Main Whitelist</li>
-            <li>Final Whitelist</li>
-            <li>Domain Whitelist</li>
-            <li>Domain Initial Whitelist</li>
-            <li>Domain Final Whitelist</li>
-            <li>Third-Party Whitelist</li>
-            <li>Third-Party Domain Whitelist</li>
-            <li>Third-Party Domain Initial Whitelist</li>
-            <li>Main Blacklist</li>
-            <li>Initial Blacklist</li>
-            <li>Final Blacklist</li>
-            <li>Domain Blacklist</li>
-            <li>Domain Initial Blacklist</li>
-            <li>Domain Final Blacklist</li>
-            <li>Domain Regular Expression Blacklist</li>
-            <li>Third-Party Blacklist</li>
-            <li>Third-Party Initial Blacklist</li>
-            <li>Third-Party Domain Blacklist</li>
-            <li>Third-Party Domain Initial Blacklist</li>
-            <li>Third-Party Regular Expression Blacklist</li>
-            <li>Third-Party Domain Regular Expression Blacklist</li>
-            <li>Regular Expression Blacklist</li>
+            <li>Haupt-Positivliste</li>
+            <li>Positivliste (URL-Ende)</li>
+            <li>Domänen-Positivliste</li>
+            <li>Domänen-Positivliste (URL-Anfang)</li>
+            <li>Domänen-Positivliste (URL-Ende)</li>
+            <li>Drittanbieter-Positivliste</li>
+            <li>Drittanbieter-Domänen-Positivliste</li>
+            <li>Drittanbieter-Domänen-Positivliste (URL-Anfang)</li>
+            <li>Haupt-Negativliste</li>
+            <li>Negativliste (URL-Anfang)</li>
+            <li>Negativliste (URL-Ende)</li>
+            <li>Domänen-Negativliste</li>
+            <li>Domänen-Negativliste (URL-Anfang)</li>
+            <li>Domänen-Negativliste (URL-Ende)</li>
+            <li>Domänen-Negativliste mit regulären Ausdrücken</li>
+            <li>Drittanbieter-Negativliste</li>
+            <li>Drittanbieter-Negativliste (URL-Anfang)</li>
+            <li>Drittanbieter-Domänen-Negativliste</li>
+            <li>Drittanbieter-Domänen-Negativliste (URL-Anfang)</li>
+            <li>Drittanbieter-Negativliste mit regulären Ausdrücken</li>
+            <li>Drittanbieter-Domänen-Negativliste mit regulären Ausdrücken</li>
+            <li>Negativliste mit regulären Ausdrücken</li>
         </ol>
 
-        <p>Initial lists check against the beginning of the URL. Final lists check against the end of the URL. Domain lists only check against certain domains.
-            Third-party lists only apply if the root domain of the request is different than the root domain of the main URL.
-            Regular expression lists follow the <a href="https://en.wikipedia.org/wiki/Regular_expression">regular expression syntax</a>. Each sublist item has one or more entry.
-            In the case of domain sublists, the resource request is only checked against the item if the first entry matches the domain of the main URL.</p>
+        <p>Listen mit "(URL-Anfang)" prüfen gegen den Anfang einer URL, solche mit "(URL-Ende)" gegen das Ende der URL. Domänen-Listen prüfen gegen bestimmte Domains.
+            Drittanbieter-Listen greifen nur, wenn die Domain der Anfrage eine andere ist als die Domain der aufgerufenen URL.
+            Listen mit regulären Ausdrücken folgen der <a href="https://de.wikipedia.org/wiki/Reguläre_Ausdrücke">Syntax für reguläre Ausdrücke</a>. Jede Unter-Liste hat dabei einen oder mehrere Einträge.
+            Bei Domänen-Unterlisten werden die Ressourcen-Zugriffe nur dann überprüft, wenn der erste Eintrag der Domain der aufgerufenen URL entspricht.</p>
 
-        <p>Because of limitations in Android’s WebView, and to speed up processing of requests, Privacy Browser implements a simplified interpretation of the Adblock syntax.
-            This can sometimes lead to false positives, where resources are allowed or blocked in ways that weren’t intended by the original entry.
-            A more detailed description of how the blocklist entries are processed is available at <a href="https://www.stoutner.com/privacy-browser/blocklists/">stoutner.com</a>.</p>
+        <p>Aufgrund von Einschränkungen in Android’s WebView und um die Geschwindigkeit von Anfragen zu steigern, verwendet Privacy Browser eine vereinfachte Auswertung der Adblock-Syntax.
+            Dies kann manchmal zu "false positives" führen, bei denen Ressourcen erlaubt oder blockiert werden, die in den originalen Einträgen anders intendiert wären.
+            Eine detailiertere Beschreibung, wie die Listen-Einträge abgearbeitet werden, ist unter <a href="https://www.stoutner.com/privacy-browser/blocklists/">stoutner.com</a> verfügbar.</p>
 
-        <p>Privacy Browser has two additional blocklists,
-            one called <a href="https://www.stoutner.com/privacy-browser/blocklists/ultraprivacy/">UltraPrivacy</a> that blocks trackers that EasyPrivacy allows,
-            and the other that blocks all third-party requests.
-            A request is only considered third-party if the base domain of the request is different than the base domain of the URL.
-            For example, if <code>www.website.com</code> loads a picture from <code>images.website.com</code>,
-            this is not blocked as a third-party request because they both share the same base domain of <code>website.com</code>.
-            Blocking all third-party requests increases privacy, but this blocklist is disabled by default because it breaks a large number of websites.</p>
+        <p>Privacy Browser hat zwei weitere Sperrlisten: <a href="https://www.stoutner.com/privacy-browser/blocklists/ultraprivacy/">UltraPrivacy</a>, welche Tracker blockiert, die EasyPrivacy erlaubt,
+            und eine andere, die alle Drittanbieter-Anfragen blockiert.
+            Eine Anfrage wird dabei nur als Drittanbieter-Anfrage gewertet, wenn die Basis-Domain der Anfrage eine andere ist als die Basis-Domain der angefragten URL.
+            Lädt zum Beispiel die Seite <code>www.website.com</code> ein Bild von <code>images.website.com</code>, wird diese Anfrage nicht als Drittanbieter-Anfrage gewertet,
+            da die beiden Anfragen dieselbe Basis-Domain <code>website.com</code> haben.
+            Alle Drittanbieter-Anfragen zu blockieren verbessert zwar die Privatsphäre, diese Einstellung ist jedoch standardmäßig deaktiviert, da dadurch viele Webseiten verunstaltet werden können.</p>
     </body>
 </html>
\ No newline at end of file
index 937c7951f5b323563b7efb7a144207bad50c5024..848d6304209e2659402b18b6b7009d55d355501b 100644 (file)
@@ -1,6 +1,8 @@
 <!--
   Copyright © 2017-2019 Soren Stoutner <soren@stoutner.com>.
 
+  Translation 2019 Bernhard G. Keller.  Copyright assigned to 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
     </head>
 
     <body>
-        <h3><img class="title" src="../shared_images/vpn_lock_blue_dark.png"> Connect with Confidence</h3>
+        <h3><img class="title" src="../shared_images/vpn_lock_blue_dark.png"> Verbindungen mit Vertrauen</h3>
 
-        <p>When visiting an encrypted URL (one that begins with HTTPS), the webserver uses an SSL certificate to both encrypt the information sent to the browser and to identify the server.
-            The purpose of the server identification is to prevent a machine located between the browser and the webserver from pretending to be the server and decrypting the information in transit.
-            This type of attack is known as a Man In The Middle (MITM) attack. SSL certificates are generated by certificate authorities: companies that verify a server’s identity and produce a certificate for a fee.
-            Android has a list of trusted certificate authorities, and will accept any of their certificates for any website.
-            It isn’t supposed to be possible for an organization to acquire an SSL certificate for a domain they do not control, but in practice many governments and large corporations have been able to do so.</p>
+        <p>Wenn Sie verschlüsselte URLs (also solche, die mit HTTPS beginnen) besuchen, verwendet der Webserver ein SSL-Zertifikat,
+            um einerseits die zum Browser gesendeten Informationen zu verschlüsseln und andererseits um sich selbst zu identifizieren.
+                       Der Zweck der Server-Identifikation ist dabei, zu verhindern, dass ein Rechner zwischen Server und Ihrem Browser geschaltet werden kann,
+            der vorgibt der Server zu sein und die übertragenen Informationen auf dem Transportweg entschlüsseln oder verändern kann. Solche Angriffe werden als "Man-in-the-Middle-Atacken" (MITM) bezeichnet.
+                       SSL-Zertifikate werden von Zertifikats-Stellen generiert, d.h. Unternehmen, die die Identität eines Servers überprüfen und dafür (meist gegen Entgelt) ein Zertifikat ausstellen.
+                       Android beinhaltet eine Liste zuverlässiger Zertifikats-Stellen und akzeptiert jedes von einer solchen ausgestellte Zertifikat einer Webseite.
+                       Es wird dabei davon ausgegangen, dass eine Organisation kein SSL-Zertifikat für eine Domain beantragen kann, die nicht ihr gehört.
+            In der Praxis konnten dies jedoch bereits viele Regierungen und große Unternehmen tun.</p>
 
-        <p>Pinning an SSL certificate tells the browser that only one specific SSL certificate is to be trusted for a particular domain. Any other certificate, even if it is valid, will be rejected.</p>
+        <p>Mittels Zertifikats-Verankerung ("Pinning") kann Privacy Browser angewiesen werden, nur ein spezielles SSL-Zertifikat für eine Domäne zuzulassen.
+            Jedes andere Zertifikat - auch wenn dies gültig ist - wird in diesem Fall abgelehnt.</p>
 
         <p><img class="center21" src="images/pinned_mismatch.png"></p>
 
-        <p>SSL certificates expire on a specified date, so even pinned SSL certificates will legitimately need to be updated from time to time.
-            As a general rule, pinning SSL certificates probably isn’t needed in the majority of cases.
-            But for those who suspect that powerful organizations may be targeting them, SSL certificate pinning can detect and thwart a MITM attack.
-            Privacy Browser also has the ability to pin IP addresses.</p>
+        <p>SSL-Zertifikate laufen zu einem festgelegten Datum ab, sodass auch verankerte Zertifikate legitimerweise von Zeit zu Zeit aktualisiert werden müssen.
+                       In der Regel müssen SSL-Zertifikate in der Mehrzahl der Fälle nicht verankert werden. Für jene, die jedoch damit rechnen müssen, dass mächtige Organisationen auf sie abzielen,
+            kann das verankern von SSL-Zertifikaten Man-in-the-middle-Attacken aufdecken und ggf. vereiteln. Privacy Browser bietet auch die Möglichkeit, IP-Adressen zu verankern.</p>
 
         <p><img class="center21" src="images/pinned_ssl_certificate.png"></p>
-
-        <p>SSL certificates can be pinned in Domain Settings.
-            Besides protecting against MITM attacks, pinning a self-signed certificate for a device like a wireless router or access point will remove the error message that is normally presented every time its website is loaded.
-            To view the current website SSL certificate, tap on the favorite icon next to the URL bar.</p>
+        
+        <p>SSL-Zertifikate können in den Domänen-Einstellungen verankert werden.
+                       Neben dem Schutz gegen MITM-Attacken kann das Verankern von Zertifikaten auch für selbst-erzeugte Zertifikate genutzt werden (z.B. bei WLAN-Routern oder Access-Points).
+            In diesen Fällen werden die sonst üblichen Fehlermeldungen unterdrückt, wenn die Website des Geräts geladen wird.
+                       Um das aktuelle SSL-Zertifikat einer Webseite anzusehen, tippen Sie bitte auf das Lesezeichen-Icon neben der URL-Leiste.</p>
     </body>
 </html>
\ No newline at end of file
index 72cce3e99d98247eec728eab8644a099890df011..bcd69decaeb9d34c014fa6d765eca80c00c1f6b7 100644 (file)
@@ -1,6 +1,8 @@
 <!--
   Copyright © 2017-2019 Soren Stoutner <soren@stoutner.com>.
 
+  Translation 2019 Bernhard G. Keller.  Copyright assigned to 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
     </head>
 
     <body>
-        <h3><img class="title" src="../shared_images/vpn_lock_blue_light.png"> Connect with Confidence</h3>
+        <h3><img class="title" src="../shared_images/vpn_lock_blue_light.png"> Verbindungen mit Vertrauen</h3>
 
-        <p>When visiting an encrypted URL (one that begins with HTTPS), the webserver uses an SSL certificate to both encrypt the information sent to the browser and to identify the server.
-            The purpose of the server identification is to prevent a machine located between the browser and the webserver from pretending to be the server and decrypting the information in transit.
-            This type of attack is known as a Man In The Middle (MITM) attack. SSL certificates are generated by certificate authorities: companies that verify a server’s identity and produce a certificate for a fee.
-            Android has a list of trusted certificate authorities, and will accept any of their certificates for any website.
-            It isn’t supposed to be possible for an organization to acquire an SSL certificate for a domain they do not control, but in practice many governments and large corporations have been able to do so.</p>
+        <p>Wenn Sie verschlüsselte URLs (also solche, die mit HTTPS beginnen) besuchen, verwendet der Webserver ein SSL-Zertifikat,
+            um einerseits die zum Browser gesendeten Informationen zu verschlüsseln und andererseits um sich selbst zu identifizieren.
+            Der Zweck der Server-Identifikation ist dabei, zu verhindern, dass ein Rechner zwischen Server und Ihrem Browser geschaltet werden kann,
+            der vorgibt der Server zu sein und die übertragenen Informationen auf dem Transportweg entschlüsseln oder verändern kann. Solche Angriffe werden als "Man-in-the-Middle-Atacken" (MITM) bezeichnet.
+            SSL-Zertifikate werden von Zertifikats-Stellen generiert, d.h. Unternehmen, die die Identität eines Servers überprüfen und dafür (meist gegen Entgelt) ein Zertifikat ausstellen.
+            Android beinhaltet eine Liste zuverlässiger Zertifikats-Stellen und akzeptiert jedes von einer solchen ausgestellte Zertifikat einer Webseite.
+            Es wird dabei davon ausgegangen, dass eine Organisation kein SSL-Zertifikat für eine Domain beantragen kann, die nicht ihr gehört.
+            In der Praxis konnten dies jedoch bereits viele Regierungen und große Unternehmen tun.</p>
 
-        <p>Pinning an SSL certificate tells the browser that only one specific SSL certificate is to be trusted for a particular domain. Any other certificate, even if it is valid, will be rejected.</p>
+        <p>Mittels Zertifikats-Verankerung ("Pinning") kann Privacy Browser angewiesen werden, nur ein spezielles SSL-Zertifikat für eine Domäne zuzulassen.
+            Jedes andere Zertifikat - auch wenn dies gültig ist - wird in diesem Fall abgelehnt.</p>
 
         <p><img class="center21" src="images/pinned_mismatch.png"></p>
 
-        <p>SSL certificates expire on a specified date, so even pinned SSL certificates will legitimately need to be updated from time to time.
-            As a general rule, pinning SSL certificates probably isn’t needed in the majority of cases.
-            But for those who suspect that powerful organizations may be targeting them, SSL certificate pinning can detect and thwart a MITM attack.
-            Privacy Browser also has the ability to pin IP addresses.</p>
+        <p>SSL-Zertifikate laufen zu einem festgelegten Datum ab, sodass auch verankerte Zertifikate legitimerweise von Zeit zu Zeit aktualisiert werden müssen.
+            In der Regel müssen SSL-Zertifikate in der Mehrzahl der Fälle nicht verankert werden. Für jene, die jedoch damit rechnen müssen, dass mächtige Organisationen auf sie abzielen,
+            kann das verankern von SSL-Zertifikaten Man-in-the-middle-Attacken aufdecken und ggf. vereiteln. Privacy Browser bietet auch die Möglichkeit, IP-Adressen zu verankern.</p>
 
         <p><img class="center21" src="images/pinned_ssl_certificate.png"></p>
 
-        <p>SSL certificates can be pinned in Domain Settings.
-            Besides protecting against MITM attacks, pinning a self-signed certificate for a device like a wireless router or access point will remove the error message that is normally presented every time its website is loaded.
-            To view the current website SSL certificate, tap on the favorite icon next to the URL bar.</p>
+        <p>SSL-Zertifikate können in den Domänen-Einstellungen verankert werden.
+            Neben dem Schutz gegen MITM-Attacken kann das Verankern von Zertifikaten auch für selbst-erzeugte Zertifikate genutzt werden (z.B. bei WLAN-Routern oder Access-Points).
+            In diesen Fällen werden die sonst üblichen Fehlermeldungen unterdrückt, wenn die Website des Geräts geladen wird.
+            Um das aktuelle SSL-Zertifikat einer Webseite anzusehen, tippen Sie bitte auf das Lesezeichen-Icon neben der URL-Leiste.</p>
     </body>
 </html>
\ No newline at end of file
index 7305ec0f6ea62f9a8f7fb94c243418ed8ee1c7b6..9daf83c7bd5061d5c4391869dab9f545756ec2b9 100644 (file)
@@ -1,6 +1,8 @@
 <!--
   Copyright © 2016-2018 Soren Stoutner <soren@stoutner.com>.
 
+  Translation 2019 Bernhard G. Keller.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+
   Translation 2016 Aaron Gerlach <aaron@gerlach.com>.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
     <body>
         <h3><img class="title" src="../shared_images/vpn_key_blue_dark.png"> Tor und seine Grenzen</h3>
 
-        <p>Faktoren, die in die Privatsphäre im Web eindringen wollen lassen sich hauptsächlich in zwei verschiedene Kategorien unterteilen:
-            Neugierige Behörden mit Zugriff auf Internetprovider und Megafirmen, die Soziale und Werbenetzwerke betreiben.
-            TOR (The Onion Router) ist nützlich, wenn es auf den Schutz der Privatsphäre vor neugierigen Behörden ankommt, aber nicht bei den Megafirmen.</p>
+        <p>Faktoren, die die Privatsphäre im Web einschränken wollen, lassen sich hauptsächlich in zwei verschiedene Kategorien unterteilen:
+            Neugierige Behörden mit Zugriff auf Internetprovider und grosse Konzerne, die "soziale" und Werbe-Netzwerke betreiben.
+            TOR (The Onion Router) ist nützlich, wenn es auf den Schutz der Privatsphäre vor neugierigen Behörden ankommt, nicht jedoch bei Konzernen bzw. Megafirmen.</p>
 
 
         <h3><img class="title" src="../shared_images/language_blue_dark.png"> Neugierige Behörden</h3>
 
-        <p>Oft spähen neugierige Behörden die Bürger aus, um Fehlverhalten und Menschenrechtsaktivitäten zu bestrafen.
-            Üblicherweise betreiben sie entweder die Internetprovider selber oder sie können die Provider dazu zwingen, Informationen über jede besuchte IP-Adresse und die zugehörigen Benutzer herauszugeben.
+        <p>Oft spähen neugierige Behörden Bürger aus, um Fehlverhalten und Menschenrechtsaktivitäten zu bestrafen.
+            Üblicherweise betreiben sie entweder die Internetprovider selbst oder sie können Provider dazu zwingen, Informationen über jede besuchte IP-Adresse und die zugehörigen Benutzer herauszugeben.
             TOR wurde entwickelt, um diese Eingriffe in die Privatsphäre zu bekämpfen, indem es die Daten verschlüsselt, die ein Nutzer sendet und sie durch viele verschiedene Server schleust,
             bevor sie die Zieladresse erreichen.
             Das bedeutet, dass kein individueller Internetprovider, Server oder Website sowohl die <a href="https://ipleak.net">IP-Adresse des Nutzers</a>
@@ -44,7 +46,7 @@
             Neugierige Behörden und die von ihnen kontrollierten Internetprovider können nur vermuten, welche Webserver ein Benutzer besucht, da sie letztendlich nur sehen, dass er TOR benutzt.
             In einigen Teilen der Welt könnte das Benutzen von TOR als illegale Aktivität ausgelegt werden ("wenn Du nichts zu verstecken hättest,
             würdest Du nicht Deinen Traffic vor uns verstecken") und Benutzer könnten bestraft werden, da die Regierung vermutet, sie würden etwas Untersagtes tun.
-            Also kann TOR hilfreich sein, ist jedoch kein Allheilmittel.</p>
+            Also kann TOR hilfreich sein, ein Allheilmittel ist es jedoch nicht.</p>
 
 
         <h3><img class="title" src="../shared_images/language_blue_dark.png"> Megafirmen</h3>
         <p>Wenn ein Benutzer sich mit einem Webserver verbindet, kann der Webserver seine IP-Adresse sehen.
             Obwohl es keine ausgereifte Methode ist, können IP-Adressen in physische Adressen umgewandelt werden - <a href="https://www.whatismyip.com/">mit erstaunlicher Genauigkeit</a>.
             Kleine Webserver nutzen normalerweise die IP-Adresse, um festzulegen, woher der Benutzer die Seite besucht. TOR ist eine gute Lösung, wenn man seinen Standort vor diesen Servern verstecken möchte.
-            Große Megafirmen, die Soziale Netzwerke und Werbenetzwerke betreiben nutzen jedoch richtige Profile an Informationen, um Benutzer über ihre Geräte und IP-Adressen hinaus zu tracken.
-            Diese Profile nutzen verschiedene Möglichkeiten an Techniken um Benutzer ausfindig zu machen, inklusive JavaScript, Cookies, Tracking-IDs und ein
+            Große Megafirmen, die "soziale" und Werbe-Netzwerke betreiben, nutzen jedoch Profile an Informationen, um Benutzer über ihre Geräte und IP-Adressen hinweg zu tracken.
+            Diese Profile nutzen verschiedene Techniken um Benutzer ausfindig zu machen, inklusive JavaScript, Cookies, Tracking-IDs und den sogenannten
             <a href="https://panopticlick.eff.org/">"Browser-Fingerabdruck" (fingerprinting)</a>.
-            Weil die große Mehrheit der Websites im Internet eine Werbung entweder von den großen Werbenetzwerken oder von eingebetteten Social-Media-Buttons mit ihrem zugewiesenen JavaScript lädt,
-            haben diese großen Megafirmen Profile von so gut wie jedem Internetnutzer angefertigt und verfolgen ihre Aktivitäten über zueinander unabhängige Seiten.</p>
+            Weil die große Mehrheit der Websites im Internet Werbung entweder von den großen Werbenetzwerken oder von eingebetteten Social-Media-Buttons mit ihrem zugewiesenen JavaScript lädt,
+            haben die großen Megafirmen Profile von so gut wie jedem Internetnutzer angefertigt und verfolgen deren Aktivitäten über zueinander unabhängige Seiten.</p>
 
-        <p>Sie verfolgen jede besuchte Seite, alles online Gekaufte, jede für Einkäufe genutzte Kreditkarte, jede Adresse,
-            an die Waren verschickt werden und die GPS-Metadaten von jedem ins Internet hochgeladene Foto.
-            Sie fertigen Profile an von Alter, Geschlecht, Beziehungsstatus, Adresse, politischen Ansichten, religiösen Ansichten, familiären Zuständen,
-            Anzahl der Haustiere und allem anderen was sie über einen Nutzer herausfinden können.
+        <p>Sie verfolgen jede besuchte Seite, Alles online Gekaufte, jede für Einkäufe genutzte Kreditkarte, jede Adresse,
+            an die Waren verschickt werden, und die GPS-Metadaten von jedem ins Internet hochgeladene Foto.
+            Sie fertigen Profile an, die Alter, Geschlecht, Beziehungsstatus, Adresse, politische und religiöse Ansichten, familiäre Umstände,
+            Anzahl der Haustiere und alles Andere, was sie über einen Nutzer herausfinden können, beinhalten.
             Sie kaufen sogar ganze Datenbanken mit Informationen über Kreditkartennutzungen in Geschäften auf, damit sie Offline-Einkaufsverhalten von Nutzern in ihren Profilen nachverfolgen können.
-            Weil sie bereits weitaus genauere Adressinformationen über einen Nutzer haben als eine IP-Adresse aussagt, bietet TOR keinen echten Schutz der Privatsphäre gegen Megafirmen.</p>
+            Weil sie bereits weitaus genauere Adressinformationen über einen Nutzer haben, als eine IP-Adresse aussagt, bietet TOR keinen echten Schutz der Privatsphäre gegen derartige Megafirmen.</p>
 
         <p>Der einzige und beste Schutz der Privatsphäre gegen Megafirmen ist es, mit deaktivieren JavaScript durchs Web zu surfen, gefolgt von geblockten Werbenetzwerken,
-            deaktivieren Cookies und DOM-Speicher und das Benutzen eines Browsers, der schwierig zu "fingerprinten" ist.</p>
+            deaktivierten Cookies und DOM-Speicher und das Benutzen eines Browsers, der schwierig zu "fingerprinten" ist.</p>
 
 
         <h3><img class="title" src="../shared_images/orbot_blue_dark.png"> Benutzen von TOR</h3>
 
-        <p>Neben den Einschränkungen kann TOR in bestimmten Situationen hilfreich sein.
-            Das TOR Project hat eine App für Android namens Orbot, die auf <a href="https://f-droid.org/repository/browse/?fdfilter=orbot&fdid=org.torproject.android">F-Droid</a>
-            verfügbar ist und überall sonst wo Privacy Browser angeboten wird. Privacy Browser hat eine Option um Orbot als Proxy zu benutzen.
-            Wenn diese aktiviert wird, hat Privacy Browser einen hellblauen Hintergrund bei der Adresszeile statt dem standardmäßigen Hellgrau.
-            Wenn Privacy Browsers Orbot-Proxy-Einstellung aktiviert ist, wird das Internet ausschließlich funktionieren, solange Orbot ausgeführt und mit dem TOR-Netzwerk verbunden wird.
-            Weil die Daten durch verschiedene Anlaufstellen geleitet werden ist das Nutzen von TOR oft weitaus langsamer als das direkte Verbinden mit dem Internet. </p>
+        <p>Neben den vorhin genannten Einschränkungen kann TOR in bestimmten Situationen hilfreich sein.
+            Das TOR Project bietet eine App für Android namens Orbot an, die auf <a href="https://f-droid.org/repository/browse/?fdfilter=orbot&fdid=org.torproject.android">F-Droid</a>
+            verfügbar ist und überall sonst, wo Privacy Browser angeboten wird. Privacy Browser hat eine Option, um Orbot als Proxy zu benutzen.
+            Wenn diese aktiviert wird, hat Privacy Browser einen hellblauen Hintergrund für die Adresszeile statt dem standardmäßigen Hellgrau.
+            Wenn Privacy Browsers Orbot-Proxy-Einstellung aktiviert ist, wird das Internet ausschließlich funktionieren, solange Orbot ausgeführt und mit dem TOR-Netzwerk verbunden ist.
+            Weil die Daten durch verschiedene Anlaufstellen geleitet werden, ist das Nutzen von TOR oft weitaus langsamer als bei einer direkten Verbindung mit dem Internet.</p>
 
         <img class="center" src="images/tor.png">
 
-        <h3><img class="title" src="../shared_images/file_download_blue_dark.png"> Downloading Files Via Tor</h3>
-        <p>When Orbot is operating in proxy mode, browsing the internet using Privacy Browser will be routed through the Tor network, but file downloads will not.
-            This is because Privacy Browser uses Android’s builtin download manager to download files, which doesn't have a proxy option.
-            Users who want to download files via Orbot need to enable its VPN mode.</p>
+
+        <h3><img class="title" src="../shared_images/file_download_blue_dark.png"> Herunterladen von Dateien mittels TOR</h3>
+        <p>Wenn Orbot im Proxy-Modus genutzt wird, werden zwar beim Surfen mit dem Privacy Browser die Daten über das TOR-Netzwerk geroutet, nicht jedoch heruntergeladene Dateien.
+            Die Ursache dafür ist, dass Privacy Browser den in Android eingebauten Download-Manager verwendet, der keine Proxy-Optionen hat.
+            Benutzer, die auch Dateien über Orbot herunterladen möchten, müssen daher dessen VPN-Modus aktivieren.</p>
 
         <img class="center21" src="../shared_images/vpn_mode.png">
     </body>
index c69c203b27e311ad41cb9f3cffb387c30888fc89..0c1bb26e36a9af6dc84b3e21480b0b62b07d6a95 100644 (file)
@@ -1,6 +1,8 @@
 <!--
   Copyright © 2016-2018 Soren Stoutner <soren@stoutner.com>.
 
+  Translation 2019 Bernhard G. Keller.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+
   Translation 2016 Aaron Gerlach <aaron@gerlach.com>.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
     <body>
         <h3><img class="title" src="../shared_images/vpn_key_blue_light.png"> Tor und seine Grenzen</h3>
 
-        <p>Faktoren, die in die Privatsphäre im Web eindringen wollen lassen sich hauptsächlich in zwei verschiedene Kategorien unterteilen:
-            Neugierige Behörden mit Zugriff auf Internetprovider und Megafirmen, die Soziale und Werbenetzwerke betreiben.
-            TOR (The Onion Router) ist nützlich, wenn es auf den Schutz der Privatsphäre vor neugierigen Behörden ankommt, aber nicht bei den Megafirmen.</p>
+        <p>Faktoren, die die Privatsphäre im Web einschränken wollen, lassen sich hauptsächlich in zwei verschiedene Kategorien unterteilen:
+            Neugierige Behörden mit Zugriff auf Internetprovider und grosse Konzerne, die "soziale" und Werbe-Netzwerke betreiben.
+            TOR (The Onion Router) ist nützlich, wenn es auf den Schutz der Privatsphäre vor neugierigen Behörden ankommt, nicht jedoch bei Konzernen bzw. Megafirmen.</p>
 
 
         <h3><img class="title" src="../shared_images/language_blue_light.png"> Neugierige Behörden</h3>
 
-        <p>Oft spähen neugierige Behörden die Bürger aus, um Fehlverhalten und Menschenrechtsaktivitäten zu bestrafen.
-            Üblicherweise betreiben sie entweder die Internetprovider selber oder sie können die Provider dazu zwingen, Informationen über jede besuchte IP-Adresse und die zugehörigen Benutzer herauszugeben.
+        <p>Oft spähen neugierige Behörden Bürger aus, um Fehlverhalten und Menschenrechtsaktivitäten zu bestrafen.
+            Üblicherweise betreiben sie entweder die Internetprovider selbst oder sie können Provider dazu zwingen, Informationen über jede besuchte IP-Adresse und die zugehörigen Benutzer herauszugeben.
             TOR wurde entwickelt, um diese Eingriffe in die Privatsphäre zu bekämpfen, indem es die Daten verschlüsselt, die ein Nutzer sendet und sie durch viele verschiedene Server schleust,
             bevor sie die Zieladresse erreichen.
             Das bedeutet, dass kein individueller Internetprovider, Server oder Website sowohl die <a href="https://ipleak.net">IP-Adresse des Nutzers</a>
@@ -44,7 +46,7 @@
             Neugierige Behörden und die von ihnen kontrollierten Internetprovider können nur vermuten, welche Webserver ein Benutzer besucht, da sie letztendlich nur sehen, dass er TOR benutzt.
             In einigen Teilen der Welt könnte das Benutzen von TOR als illegale Aktivität ausgelegt werden ("wenn Du nichts zu verstecken hättest,
             würdest Du nicht Deinen Traffic vor uns verstecken") und Benutzer könnten bestraft werden, da die Regierung vermutet, sie würden etwas Untersagtes tun.
-            Also kann TOR hilfreich sein, ist jedoch kein Allheilmittel.</p>
+            Also kann TOR hilfreich sein, ein Allheilmittel ist es jedoch nicht.</p>
 
 
         <h3><img class="title" src="../shared_images/language_blue_light.png"> Megafirmen</h3>
         <p>Wenn ein Benutzer sich mit einem Webserver verbindet, kann der Webserver seine IP-Adresse sehen.
             Obwohl es keine ausgereifte Methode ist, können IP-Adressen in physische Adressen umgewandelt werden - <a href="https://www.whatismyip.com/">mit erstaunlicher Genauigkeit</a>.
             Kleine Webserver nutzen normalerweise die IP-Adresse, um festzulegen, woher der Benutzer die Seite besucht. TOR ist eine gute Lösung, wenn man seinen Standort vor diesen Servern verstecken möchte.
-            Große Megafirmen, die Soziale Netzwerke und Werbenetzwerke betreiben nutzen jedoch richtige Profile an Informationen, um Benutzer über ihre Geräte und IP-Adressen hinaus zu tracken.
-            Diese Profile nutzen verschiedene Möglichkeiten an Techniken um Benutzer ausfindig zu machen, inklusive JavaScript, Cookies, Tracking-IDs und ein
+            Große Megafirmen, die "soziale" und Werbe-Netzwerke betreiben, nutzen jedoch Profile an Informationen, um Benutzer über ihre Geräte und IP-Adressen hinweg zu tracken.
+            Diese Profile nutzen verschiedene Techniken um Benutzer ausfindig zu machen, inklusive JavaScript, Cookies, Tracking-IDs und den sogenannten
             <a href="https://panopticlick.eff.org/">"Browser-Fingerabdruck" (fingerprinting)</a>.
-            Weil die große Mehrheit der Websites im Internet eine Werbung entweder von den großen Werbenetzwerken oder von eingebetteten Social-Media-Buttons mit ihrem zugewiesenen JavaScript lädt,
-            haben diese großen Megafirmen Profile von so gut wie jedem Internetnutzer angefertigt und verfolgen ihre Aktivitäten über zueinander unabhängige Seiten.</p>
+            Weil die große Mehrheit der Websites im Internet Werbung entweder von den großen Werbenetzwerken oder von eingebetteten Social-Media-Buttons mit ihrem zugewiesenen JavaScript lädt,
+            haben die großen Megafirmen Profile von so gut wie jedem Internetnutzer angefertigt und verfolgen deren Aktivitäten über zueinander unabhängige Seiten.</p>
 
-        <p>Sie verfolgen jede besuchte Seite, alles online Gekaufte, jede für Einkäufe genutzte Kreditkarte, jede Adresse,
-            an die Waren verschickt werden und die GPS-Metadaten von jedem ins Internet hochgeladene Foto.
-            Sie fertigen Profile an von Alter, Geschlecht, Beziehungsstatus, Adresse, politischen Ansichten, religiösen Ansichten, familiären Zuständen,
-            Anzahl der Haustiere und allem anderen was sie über einen Nutzer herausfinden können.
+        <p>Sie verfolgen jede besuchte Seite, Alles online Gekaufte, jede für Einkäufe genutzte Kreditkarte, jede Adresse,
+            an die Waren verschickt werden, und die GPS-Metadaten von jedem ins Internet hochgeladene Foto.
+            Sie fertigen Profile an, die Alter, Geschlecht, Beziehungsstatus, Adresse, politische und religiöse Ansichten, familiäre Umstände,
+            Anzahl der Haustiere und alles Andere, was sie über einen Nutzer herausfinden können, beinhalten.
             Sie kaufen sogar ganze Datenbanken mit Informationen über Kreditkartennutzungen in Geschäften auf, damit sie Offline-Einkaufsverhalten von Nutzern in ihren Profilen nachverfolgen können.
-            Weil sie bereits weitaus genauere Adressinformationen über einen Nutzer haben als eine IP-Adresse aussagt, bietet TOR keinen echten Schutz der Privatsphäre gegen Megafirmen.</p>
+            Weil sie bereits weitaus genauere Adressinformationen über einen Nutzer haben, als eine IP-Adresse aussagt, bietet TOR keinen echten Schutz der Privatsphäre gegen derartige Megafirmen.</p>
 
         <p>Der einzige und beste Schutz der Privatsphäre gegen Megafirmen ist es, mit deaktivieren JavaScript durchs Web zu surfen, gefolgt von geblockten Werbenetzwerken,
-            deaktivieren Cookies und DOM-Speicher und das Benutzen eines Browsers, der schwierig zu "fingerprinten" ist.</p>
+            deaktivierten Cookies und DOM-Speicher und das Benutzen eines Browsers, der schwierig zu "fingerprinten" ist.</p>
 
 
         <h3><img class="title" src="../shared_images/orbot_blue_light.png"> Benutzen von TOR</h3>
 
-        <p>Neben den Einschränkungen kann TOR in bestimmten Situationen hilfreich sein.
-            Das TOR Project hat eine App für Android namens Orbot, die auf <a href="https://f-droid.org/repository/browse/?fdfilter=orbot&fdid=org.torproject.android">F-Droid</a>
-            verfügbar ist und überall sonst wo Privacy Browser angeboten wird. Privacy Browser hat eine Option um Orbot als Proxy zu benutzen.
-            Wenn diese aktiviert wird, hat Privacy Browser einen hellblauen Hintergrund bei der Adresszeile statt dem standardmäßigen Hellgrau.
-            Wenn Privacy Browsers Orbot-Proxy-Einstellung aktiviert ist, wird das Internet ausschließlich funktionieren, solange Orbot ausgeführt und mit dem TOR-Netzwerk verbunden wird.
-            Weil die Daten durch verschiedene Anlaufstellen geleitet werden ist das Nutzen von TOR oft weitaus langsamer als das direkte Verbinden mit dem Internet. </p>
+        <p>Neben den vorhin genannten Einschränkungen kann TOR in bestimmten Situationen hilfreich sein.
+            Das TOR Project bietet eine App für Android namens Orbot an, die auf <a href="https://f-droid.org/repository/browse/?fdfilter=orbot&fdid=org.torproject.android">F-Droid</a>
+            verfügbar ist und überall sonst, wo Privacy Browser angeboten wird. Privacy Browser hat eine Option, um Orbot als Proxy zu benutzen.
+            Wenn diese aktiviert wird, hat Privacy Browser einen hellblauen Hintergrund für die Adresszeile statt dem standardmäßigen Hellgrau.
+            Wenn Privacy Browsers Orbot-Proxy-Einstellung aktiviert ist, wird das Internet ausschließlich funktionieren, solange Orbot ausgeführt und mit dem TOR-Netzwerk verbunden ist.
+            Weil die Daten durch verschiedene Anlaufstellen geleitet werden, ist das Nutzen von TOR oft weitaus langsamer als bei einer direkten Verbindung mit dem Internet.</p>
 
         <img class="center" src="images/tor.png">
 
-        <h3><img class="title" src="../shared_images/file_download_blue_light.png"> Downloading Files Via Tor</h3>
-        <p>When Orbot is operating in proxy mode, browsing the internet using Privacy Browser will be routed through the Tor network, but file downloads will not.
-            This is because Privacy Browser uses Android’s builtin download manager to download files, which doesn't have a proxy option.
-            Users who want to download files via Orbot need to enable its VPN mode.</p>
+
+        <h3><img class="title" src="../shared_images/file_download_blue_light.png"> Herunterladen von Dateien mittels TOR</h3>
+        <p>Wenn Orbot im Proxy-Modus genutzt wird, werden zwar beim Surfen mit dem Privacy Browser die Daten über das TOR-Netzwerk geroutet, nicht jedoch heruntergeladene Dateien.
+            Die Ursache dafür ist, dass Privacy Browser den in Android eingebauten Download-Manager verwendet, der keine Proxy-Optionen hat.
+            Benutzer, die auch Dateien über Orbot herunterladen möchten, müssen daher dessen VPN-Modus aktivieren.</p>
 
         <img class="center21" src="../shared_images/vpn_mode.png">
     </body>
index 70079102c8b14e38f5a2c6497587481b260ecfdf..496f29f6cc7e2513c5a88b699a9a820b78b4cf47 100644 (file)
@@ -1,6 +1,8 @@
 <!--
   Copyright © 2016-2018 Soren Stoutner <soren@stoutner.com>.
 
+  Translation 2019 Bernhard G. Keller.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+
   Translation 2016 Aaron Gerlach <aaron@gerlach.com>.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
     </head>
 
     <body>
-        <h3><img class="title" src="../shared_images/location_off_blue_dark.png"> Do Not Track</h3>
-
-        <p>A few years ago the W3C (World Wide Web Consortium) created a mechanism for browsers to inform web servers that they would not like to be tracked.
-            This is accomplished by including a <a href="https://en.wikipedia.org/wiki/Do_Not_Track">DNT (Do Not Track) header</a> with web requests.</p>
-
-        <p>The DNT header doesn't really provide any privacy because most web servers ignore it. For example, Yahoo, Google, Microsoft, and Facebook all ignore at least some DNT headers.</p>
+        <h3><img class="title" src="../shared_images/location_off_blue_dark.png"> Do-Not-Track</h3>
+
+               <p>Vor einigen Jahren wurde vom W3C (World Wide Web Consortium) ein Mechanismus für Internet-Browser entwickelt, der Webserver davon in Kenntnis setzen sollte,
+            dass deren Benutzer nicht getrackt werden wollen.
+                       Dies wird über den <a href="https://de.wikipedia.org/wiki/Do_Not_Track_(Software)">DNT-Header (Do-Not-Track-Header)</a> innerhalb von Ressourcen-Anfragen bewerkstelligt.</p>
+               
+               <p>Der DNT-Header bewirkt jedoch de facto so gut wie keine Privatsphäre, da er von den meisten Webservern schlicht ignoriert wird.
+            So ignorieren zum Beispiel mit Yahoo, Google, Microsoft und Facebook so gut wie alle grossen Internet-Anbieter zumindest einige DNT-Header.</p>
     </body>
 </html>
\ No newline at end of file
index 345372beb3cdef62b693a1ab2b13cab8e2fc1597..54795f1c33f2999bfd30dc9d031a33c1018aefe8 100644 (file)
@@ -1,6 +1,8 @@
 <!--
   Copyright © 2016-2018 Soren Stoutner <soren@stoutner.com>.
 
+  Translation 2019 Bernhard G. Keller.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+
   Translation 2016 Aaron Gerlach <aaron@gerlach.com>.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
     </head>
 
     <body>
-        <h3><img class="title" src="../shared_images/location_off_blue_light.png"> Do Not Track</h3>
+        <h3><img class="title" src="../shared_images/location_off_blue_light.png"> Do-Not-Track</h3>
 
-        <p>A few years ago the W3C (World Wide Web Consortium) created a mechanism for browsers to inform web servers that they would not like to be tracked.
-            This is accomplished by including a <a href="https://en.wikipedia.org/wiki/Do_Not_Track">DNT (Do Not Track) header</a> with web requests.</p>
+        <p>Vor einigen Jahren wurde vom W3C (World Wide Web Consortium) ein Mechanismus für Internet-Browser entwickelt, der Webserver davon in Kenntnis setzen sollte,
+            dass deren Benutzer nicht getrackt werden wollen.
+            Dies wird über den <a href="https://de.wikipedia.org/wiki/Do_Not_Track_(Software)">DNT-Header (Do-Not-Track-Header)</a> innerhalb von Ressourcen-Anfragen bewerkstelligt.</p>
 
-        <p>The DNT header doesn't really provide any privacy because most web servers ignore it. For example, Yahoo, Google, Microsoft, and Facebook all ignore at least some DNT headers.</p>
+        <p>Der DNT-Header bewirkt jedoch de facto so gut wie keine Privatsphäre, da er von den meisten Webservern schlicht ignoriert wird.
+            So ignorieren zum Beispiel mit Yahoo, Google, Microsoft und Facebook so gut wie alle grossen Internet-Anbieter zumindest einige DNT-Header.</p>
     </body>
 </html>
\ No newline at end of file
index 6f720c93a2a3bde760d33fa5805fa63bd00a39b5..4c64910b2f2a016346f8c3497e4d3afe993fed45 100644 (file)
@@ -1,6 +1,8 @@
 <!--
   Copyright © 2016-2018 Soren Stoutner <soren@stoutner.com>.
 
+  Translation 2019 Bernhard G. Keller.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+
   Translation 2016 Aaron Gerlach <aaron@gerlach.com>.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
 
     <body>
         <h3><img class="title" src="../shared_images/devices_other_blue_dark.png"> Browser-Identifikation</h3>
-
-        <p>When web browsers connect to websites, they send a user agent, which identifies the browser and the rendering capabilities it possesses.
-            The web server can use this information to decide which version of the website to send to the browser.
-            For example, many websites have different versions for desktop and mobile browsers.</p>
-
-        <p>By default, Privacy Browser uses its own user agent, which is <code>PrivacyBrowser/1.0</code>. This sends a minimum of information to the web server.
-            Because web servers do not recognize this to be a mobile user agent, they typically display the desktop version of the site.</p>
-
-        <p>By comparison, WebView’s default user agent divulges a large amount of information about the hardware and software of the device.
-            On the <strong>Settings</strong> screen, selecting <strong>WebView Default</strong> as the <strong>User agent</strong> displays the user agent that will be sent.
-            The screenshot below shows a Pixel 2 XL running Android 9 with Android System WebView 72.0.3626.105 installed.
-            Most web servers will recognize this as a mobile browser and will display the mobile version of the site if they have one.</p>
+        
+        <p>Wenn sich Internet-Browser mit Webseiten verbinden, senden Sie automatisch den sog. "User-Agent", welcher den Browser und dessen Anzeige-Fähigkeiten ausweist.
+                       Der Webserver kann diese Informationen dann nutzen, um verschiedene Versionen der Webseite an unterschiedliche Browser auszuliefern.
+                       Zum Beispiel haben viele Internet-Seiten unterschiedliche Versionen für Desktop- und Mobil-Browser,
+            damit die Seiten in Anhängigkeit von der Displaygrösse möglichst optimal genutzt werden können.</p>
+
+               <p>In der Voreinstellung nutzt Privacy Browser seinen eigenen User-Agent, welcher <code>PrivacyBrowser/1.0</code> lautet.
+            Damit wird lediglich ein Minimum an Informationen an den jeweiligen Webserver gesendet.
+                       Da jedoch manche Webserver diesen User-Agent nicht als mobilen Browser erkennen, liefern sie ggf. die Desktop-Version der betreffenden Webseite aus.</p>
+               
+               <p>Im Vergleich dazu plaudert der Standard-User-Agent von WebView eine grosse Menge an Informationen über die Hard- und Software des genutzten Geräts aus.
+                       Wird auf der <strong>Einstellungen-Seite</strong> von Privacy Browser <strong>WebView-Standard</strong> als <strong>User-Agent</strong> ausgewählt,
+            wird der dadurch übermittelte User-Agent angezeigt.
+                       Der Screenshot unterhalb zeigt ein Pixel 2 XL mit Android 9 und Android WebView 72.0.3626.105.
+                       Die meisten Webserver erkennen diesen User-Agent als Mobil-Browser und liefern eine Mobil-Version der Seite aus, so eine solche existiert.</p>
 
         <img class="center21" src="images/user_agent.png">
 
-        <p>There is enough information in the user agent that sometimes only a few visitors to a website will be the same.
-            If the user agent is combined with another piece of non-unique identifying information, often it results in a unique fingerprint.
-            The Electronic Frontier Foundation created a tool called <a href="https://panopticlick.eff.org/">Panopticlick</a> to demonstrate how much information can be gleaned from these sources.
-            If this test is run with JavaScript enabled the amount of information that is disclosed increases greatly.
-            <a href="https://www.browserleaks.com">Browser Leaks</a> and <a href="https://amiunique.org/">Am I Unique</a> are also good sources of information on this topic.</p>
+               <p>Der User-Agent beinhaltet normalerweise genügend Informationen, dass nur wenige Besucher einer Webseite denselben haben.
+                       Wenn der User-Agent daher mit anderen nicht-eindeutigen Identifizierungs-Informationen kombiniert wird, kann dies ausreichen, um einen eindeutigen Fingerabdruck zu ergeben.
+                       Die Electronic Frontier Foundation hat das Werkzeug <a href="https://panopticlick.eff.org/">Panopticlick</a> geschaffen,
+            um zu zeigen, wie viele Informationen aus diesen Quellen gesammelt werden kann.
+                       Wenn dieser Test mit aktiviertem JavaScript absolviert wird, steigt die Menge der offengelegten Informationen drastisch an.
+                       <a href="https://www.browserleaks.com">Browser Leaks</a> und <a href="https://amiunique.org/">Am I Unique</a> sind ebenfalls gute Quellen für Informationen zu diesem Thema.</p>
 
         <img class="center" src="../en/images/panopticlick.png">
-
-        <p>There are several preset user agents that match common browsers and operating systems. For browser fingerprinting purposes, anything that is rare is easier to track.
-            If Privacy Browser becomes common and many people use <code>PrivacyBrowser/1.0</code> as their user agent, it will be a good choice for privacy.
-            Firefox or Chrome are the most common user agents, but they auto-update and their version numbers change so quickly that it is likely the user agents included in Privacy Browser
-            will often be out of step with the majority of user agents in the server logs.</p>
-
-        <p>Some websites <a href="https://www.stoutner.com/user-agent-problems/">do not function correctly</a> if they do not recognize the user agent.
-            Using domain settings to set the user agent to <strong>WebView Default</strong>, or another user agent that is commonly recognized, usually resolves the problem.
+        
+        <p>Privacy Browser bietet verschiedene Voreinstellungen für den User-Agent an, um sich als gebräuchliche Browser und Betriebssysteme auszugeben.
+                       Dabei gilt grundsätzlich: Je unüblicher der User-Agent ist, des einfacher ist das Tracking.
+                       Wenn Privacy Browser gebräuchlicher wird und viele Personen den User-Agent <code>PrivacyBrowser/1.0</code> nutzen, wird dies eine gute Wahl für die Privatsphäre sein.
+                       Firefox und Chrome sind aktuell die gebräuchlichsten Browser.
+            Da sie jedoch häufig aktualisiert werden und in deren User-Agent auch die jeweilige Browser-Version mitsenden, kann es sein,
+            dass die in Privacy Browser mitgelieferten User-Agents für Firefox und Chrome wiederum in den Server-Logs auffallen.</p>
+               
+               <p>Manche Websites <a href="https://www.stoutner.com/user-agent-problems/">funktionieren nicht korrekt</a>, wenn sie einen User-Agent nicht erkennen.
+                       Wird für derartige Webseiten in den Domänen-Einstellungen von Privacy Browser der User-Agent <strong>WebView Standard</strong> oder ein anderer gebräuchlicher User-Agent ausgewählt,
+            verschwinden diese Probleme meist.
             Androids WebView erlaubt keinen leeren User Agent. Wenn das der Fall ist, wird der Standard-User Agent an den Server gesandt.</p>
     </body>
 </html>
\ No newline at end of file
index 56e4c3d5c74fac5251d74894a93bc6d9411d31c0..0818ea9d81d3c2b1d0fa7b9545f734bf6a976229 100644 (file)
@@ -1,6 +1,8 @@
 <!--
   Copyright © 2016-2018 Soren Stoutner <soren@stoutner.com>.
 
+  Translation 2019 Bernhard G. Keller.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+
   Translation 2016 Aaron Gerlach <aaron@gerlach.com>.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
     <body>
         <h3><img class="title" src="../shared_images/devices_other_blue_light.png"> Browser-Identifikation</h3>
 
-        <p>When web browsers connect to websites, they send a user agent, which identifies the browser and the rendering capabilities it possesses.
-            The web server can use this information to decide which version of the website to send to the browser.
-            For example, many websites have different versions for desktop and mobile browsers.</p>
+        <p>Wenn sich Internet-Browser mit Webseiten verbinden, senden Sie automatisch den sog. "User-Agent", welcher den Browser und dessen Anzeige-Fähigkeiten ausweist.
+            Der Webserver kann diese Informationen dann nutzen, um verschiedene Versionen der Webseite an unterschiedliche Browser auszuliefern.
+            Zum Beispiel haben viele Internet-Seiten unterschiedliche Versionen für Desktop- und Mobil-Browser,
+            damit die Seiten in Anhängigkeit von der Displaygrösse möglichst optimal genutzt werden können.</p>
 
-        <p>By default, Privacy Browser uses its own user agent, which is <code>PrivacyBrowser/1.0</code>. This sends a minimum of information to the web server.
-            Because web servers do not recognize this to be a mobile user agent, they typically display the desktop version of the site.</p>
+        <p>In der Voreinstellung nutzt Privacy Browser seinen eigenen User-Agent, welcher <code>PrivacyBrowser/1.0</code> lautet.
+            Damit wird lediglich ein Minimum an Informationen an den jeweiligen Webserver gesendet.
+            Da jedoch manche Webserver diesen User-Agent nicht als mobilen Browser erkennen, liefern sie ggf. die Desktop-Version der betreffenden Webseite aus.</p>
 
-        <p>By comparison, WebView’s default user agent divulges a large amount of information about the hardware and software of the device.
-            On the <strong>Settings</strong> screen, selecting <strong>WebView Default</strong> as the <strong>User agent</strong> displays the user agent that will be sent.
-            The screenshot below shows a Pixel 2 XL running Android 9 with Android System WebView 72.0.3626.105 installed.
-            Most web servers will recognize this as a mobile browser and will display the mobile version of the site if they have one.</p>
+        <p>Im Vergleich dazu plaudert der Standard-User-Agent von WebView eine grosse Menge an Informationen über die Hard- und Software des genutzten Geräts aus.
+            Wird auf der <strong>Einstellungen-Seite</strong> von Privacy Browser <strong>WebView-Standard</strong> als <strong>User-Agent</strong> ausgewählt,
+            wird der dadurch übermittelte User-Agent angezeigt.
+            Der Screenshot unterhalb zeigt ein Pixel 2 XL mit Android 9 und Android WebView 72.0.3626.105.
+            Die meisten Webserver erkennen diesen User-Agent als Mobil-Browser und liefern eine Mobil-Version der Seite aus, so eine solche existiert.</p>
 
         <img class="center21" src="images/user_agent.png">
 
-        <p>There is enough information in the user agent that sometimes only a few visitors to a website will be the same.
-            If the user agent is combined with another piece of non-unique identifying information, often it results in a unique fingerprint.
-            The Electronic Frontier Foundation created a tool called <a href="https://panopticlick.eff.org/">Panopticlick</a> to demonstrate how much information can be gleaned from these sources.
-            If this test is run with JavaScript enabled the amount of information that is disclosed increases greatly.
-            <a href="https://www.browserleaks.com">Browser Leaks</a> and <a href="https://amiunique.org/">Am I Unique</a> are also good sources of information on this topic.</p>
+        <p>Der User-Agent beinhaltet normalerweise genügend Informationen, dass nur wenige Besucher einer Webseite denselben haben.
+            Wenn der User-Agent daher mit anderen nicht-eindeutigen Identifizierungs-Informationen kombiniert wird, kann dies ausreichen, um einen eindeutigen Fingerabdruck zu ergeben.
+            Die Electronic Frontier Foundation hat das Werkzeug <a href="https://panopticlick.eff.org/">Panopticlick</a> geschaffen,
+            um zu zeigen, wie viele Informationen aus diesen Quellen gesammelt werden kann.
+            Wenn dieser Test mit aktiviertem JavaScript absolviert wird, steigt die Menge der offengelegten Informationen drastisch an.
+            <a href="https://www.browserleaks.com">Browser Leaks</a> und <a href="https://amiunique.org/">Am I Unique</a> sind ebenfalls gute Quellen für Informationen zu diesem Thema.</p>
 
         <img class="center" src="../en/images/panopticlick.png">
 
-        <p>There are several preset user agents that match common browsers and operating systems. For browser fingerprinting purposes, anything that is rare is easier to track.
-            If Privacy Browser becomes common and many people use <code>PrivacyBrowser/1.0</code> as their user agent, it will be a good choice for privacy.
-            Firefox or Chrome are the most common user agents, but they auto-update and their version numbers change so quickly that it is likely the user agents included in Privacy Browser
-            will often be out of step with the majority of user agents in the server logs.</p>
+        <p>Privacy Browser bietet verschiedene Voreinstellungen für den User-Agent an, um sich als gebräuchliche Browser und Betriebssysteme auszugeben.
+            Dabei gilt grundsätzlich: Je unüblicher der User-Agent ist, des einfacher ist das Tracking.
+            Wenn Privacy Browser gebräuchlicher wird und viele Personen den User-Agent <code>PrivacyBrowser/1.0</code> nutzen, wird dies eine gute Wahl für die Privatsphäre sein.
+            Firefox und Chrome sind aktuell die gebräuchlichsten Browser.
+            Da sie jedoch häufig aktualisiert werden und in deren User-Agent auch die jeweilige Browser-Version mitsenden, kann es sein,
+            dass die in Privacy Browser mitgelieferten User-Agents für Firefox und Chrome wiederum in den Server-Logs auffallen.</p>
 
-        <p>Some websites <a href="https://www.stoutner.com/user-agent-problems/">do not function correctly</a> if they do not recognize the user agent.
-            Using domain settings to set the user agent to <strong>WebView Default</strong>, or another user agent that is commonly recognized, usually resolves the problem.
+        <p>Manche Websites <a href="https://www.stoutner.com/user-agent-problems/">funktionieren nicht korrekt</a>, wenn sie einen User-Agent nicht erkennen.
+            Wird für derartige Webseiten in den Domänen-Einstellungen von Privacy Browser der User-Agent <strong>WebView Standard</strong> oder ein anderer gebräuchlicher User-Agent ausgewählt,
+            verschwinden diese Probleme meist.
             Androids WebView erlaubt keinen leeren User Agent. Wenn das der Fall ist, wird der Standard-User Agent an den Server gesandt.</p>
     </body>
 </html>
\ No newline at end of file
index 296d35677074ba57f1c9a5978d9d10f8d2fc84e9..0283e5b5b4fc5e2648ac212c0597eb7e6634a9b0 100644 (file)
@@ -47,6 +47,7 @@
         <p>Privacy Browser is built with the <a href="https://developer.android.com/jetpack/androidx/">AndroidX Libraries</a>
             and code from the <a href="https://mvnrepository.com/artifact/com.google.android.material/material">Google Material Maven repository</a>,
             which are released under the <a href="https://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>.</p>
+
         <p>The free flavor of Privacy Browser is built with <a href="https://mvnrepository.com/artifact/com.google.firebase/firebase-ads">Firebase Ads</a>,
             which is released under the <a href="https://developer.android.com/studio/terms">Android Software Development Kit License</a>.</p>
 
         <p><img class="icon" src="../shared_images/sort_dark.png"> sort.</p>
         <p><img class="icon" src="../shared_images/style_dark.png"> style.</p>
         <p><img class="icon" src="../shared_images/subtitles_dark.png"> subtitles.</p>
+        <p><img class="icon" src="../shared_images/tab_dark.png"> tab.</p>
         <p><img class="icon" src="../shared_images/text_fields_dark.png"> text_fields.</p>
         <p><img class="icon" src="../shared_images/thumbs_up_down_dark.png"> thumbs_up_down.</p>
         <p><img class="icon" src="../shared_images/vertical_align_bottom_dark.png"> vertical_align_bottom.</p>
index 1a29f13904683f86d28060205958b25668b037f6..2a0b87a1dc5f4b8aa3c5a075936ce83a39adb89a 100644 (file)
@@ -47,6 +47,7 @@
         <p>Privacy Browser is built with the <a href="https://developer.android.com/jetpack/androidx/">AndroidX Libraries</a>
             and code from the <a href="https://mvnrepository.com/artifact/com.google.android.material/material">Google Material Maven repository</a>,
             which are released under the <a href="https://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>.</p>
+
         <p>The free flavor of Privacy Browser is built with <a href="https://mvnrepository.com/artifact/com.google.firebase/firebase-ads">Firebase Ads</a>,
             which is released under the <a href="https://developer.android.com/studio/terms">Android Software Development Kit License</a>.</p>
 
         <p><img class="icon" src="../shared_images/sort_light.png"> sort.</p>
         <p><img class="icon" src="../shared_images/style_light.png"> style.</p>
         <p><img class="icon" src="../shared_images/subtitles_light.png"> subtitles.</p>
+        <p><img class="icon" src="../shared_images/tab_light.png"> tab.</p>
         <p><img class="icon" src="../shared_images/text_fields_light.png"> text_fields.</p>
         <p><img class="icon" src="../shared_images/thumbs_up_down_light.png"> thumbs_up_down.</p>
         <p><img class="icon" src="../shared_images/vertical_align_bottom_light.png"> vertical_align_bottom.</p>
index 937c7951f5b323563b7efb7a144207bad50c5024..e729d6b22cd1700624cb136b7411e416b0c6851d 100644 (file)
 
         <p>When visiting an encrypted URL (one that begins with HTTPS), the webserver uses an SSL certificate to both encrypt the information sent to the browser and to identify the server.
             The purpose of the server identification is to prevent a machine located between the browser and the webserver from pretending to be the server and decrypting the information in transit.
-            This type of attack is known as a Man In The Middle (MITM) attack. SSL certificates are generated by certificate authorities: companies that verify a server’s identity and produce a certificate for a fee.
+            This type of attack is known as a Man In The Middle (MITM) attack.
+            SSL certificates are generated by certificate authorities: companies that verify a server’s identity and produce a certificate for a fee.
             Android has a list of trusted certificate authorities, and will accept any of their certificates for any website.
-            It isn’t supposed to be possible for an organization to acquire an SSL certificate for a domain they do not control, but in practice many governments and large corporations have been able to do so.</p>
+            It isn’t supposed to be possible for an organization to acquire an SSL certificate for a domain they do not control,
+            but in practice many governments and large corporations have been able to do so.</p>
 
         <p>Pinning an SSL certificate tells the browser that only one specific SSL certificate is to be trusted for a particular domain. Any other certificate, even if it is valid, will be rejected.</p>
 
@@ -44,7 +46,8 @@
         <p><img class="center21" src="images/pinned_ssl_certificate.png"></p>
 
         <p>SSL certificates can be pinned in Domain Settings.
-            Besides protecting against MITM attacks, pinning a self-signed certificate for a device like a wireless router or access point will remove the error message that is normally presented every time its website is loaded.
+            Besides protecting against MITM attacks,
+            pinning a self-signed certificate for a device like a wireless router or access point will remove the error message that is normally presented every time its website is loaded.
             To view the current website SSL certificate, tap on the favorite icon next to the URL bar.</p>
     </body>
 </html>
\ No newline at end of file
index 72cce3e99d98247eec728eab8644a099890df011..4dfb257e346b64fe5ee1c6eabd2729bfc9669c23 100644 (file)
 
         <p>When visiting an encrypted URL (one that begins with HTTPS), the webserver uses an SSL certificate to both encrypt the information sent to the browser and to identify the server.
             The purpose of the server identification is to prevent a machine located between the browser and the webserver from pretending to be the server and decrypting the information in transit.
-            This type of attack is known as a Man In The Middle (MITM) attack. SSL certificates are generated by certificate authorities: companies that verify a server’s identity and produce a certificate for a fee.
+            This type of attack is known as a Man In The Middle (MITM) attack.
+            SSL certificates are generated by certificate authorities: companies that verify a server’s identity and produce a certificate for a fee.
             Android has a list of trusted certificate authorities, and will accept any of their certificates for any website.
-            It isn’t supposed to be possible for an organization to acquire an SSL certificate for a domain they do not control, but in practice many governments and large corporations have been able to do so.</p>
+            It isn’t supposed to be possible for an organization to acquire an SSL certificate for a domain they do not control,
+            but in practice many governments and large corporations have been able to do so.</p>
 
         <p>Pinning an SSL certificate tells the browser that only one specific SSL certificate is to be trusted for a particular domain. Any other certificate, even if it is valid, will be rejected.</p>
 
@@ -44,7 +46,8 @@
         <p><img class="center21" src="images/pinned_ssl_certificate.png"></p>
 
         <p>SSL certificates can be pinned in Domain Settings.
-            Besides protecting against MITM attacks, pinning a self-signed certificate for a device like a wireless router or access point will remove the error message that is normally presented every time its website is loaded.
+            Besides protecting against MITM attacks,
+            pinning a self-signed certificate for a device like a wireless router or access point will remove the error message that is normally presented every time its website is loaded.
             To view the current website SSL certificate, tap on the favorite icon next to the URL bar.</p>
     </body>
 </html>
\ No newline at end of file
index 3cef43b9681d0a8b32722060c13d395177b2a306..27318b7edb2fd27989d16594f2fd00933b30e1fb 100644 (file)
@@ -49,6 +49,7 @@
         <p>Navegador Privado está construido con las <a href="https://developer.android.com/jetpack/androidx/">librerías de AndroidX</a>
             y código del <a href="https://mvnrepository.com/artifact/com.google.android.material/material">repositorio de Google Material Maven</a>,
             que se publican bajo la <a href="https://www.apache.org/licenses/LICENSE-2.0">Licencia Apache 2.0</a>.</p>
+
         <p>El sabor o versión libre de Navegador Privado está construido con <a href="https://mvnrepository.com/artifact/com.google.firebase/firebase-ads">anuncios de Firebase</a>,
             que se libera bajo la <a href="https://developer.android.com/studio/terms">Licencia del Android Software Development Kit</a>.</p>
 
         <p><img class="icon" src="../shared_images/sort_dark.png"> sort.</p>
         <p><img class="icon" src="../shared_images/style_dark.png"> style.</p>
         <p><img class="icon" src="../shared_images/subtitles_dark.png"> subtitles.</p>
+        <p><img class="icon" src="../shared_images/tab_dark.png"> tab.</p>
         <p><img class="icon" src="../shared_images/text_fields_dark.png"> text_fields.</p>
         <p><img class="icon" src="../shared_images/thumbs_up_down_dark.png"> thumbs_up_down.</p>
         <p><img class="icon" src="../shared_images/vertical_align_bottom_dark.png"> vertical_align_bottom.</p>
index 8f2770a2fec0c3616750f5a43ff655986617f137..97b70bf80171cd80c5f738e8659cea1f67d596ca 100644 (file)
@@ -49,6 +49,7 @@
         <p>Navegador Privado está construido con las <a href="https://developer.android.com/jetpack/androidx/">librerías de AndroidX</a>
             y código del <a href="https://mvnrepository.com/artifact/com.google.android.material/material">repositorio de Google Material Maven</a>,
             que se publican bajo la <a href="https://www.apache.org/licenses/LICENSE-2.0">Licencia Apache 2.0</a>.</p>
+
         <p>El sabor o versión libre de Navegador Privado está construido con <a href="https://mvnrepository.com/artifact/com.google.firebase/firebase-ads">anuncios de Firebase</a>,
             que se libera bajo la <a href="https://developer.android.com/studio/terms">Licencia del Android Software Development Kit</a>.</p>
 
         <p><img class="icon" src="../shared_images/sort_light.png"> sort.</p>
         <p><img class="icon" src="../shared_images/style_light.png"> style.</p>
         <p><img class="icon" src="../shared_images/subtitles_light.png"> subtitles.</p>
+        <p><img class="icon" src="../shared_images/tab_dark.png"> tab.</p>
         <p><img class="icon" src="../shared_images/text_fields_light.png"> text_fields.</p>
         <p><img class="icon" src="../shared_images/thumbs_up_down_light.png"> thumbs_up_down.</p>
         <p><img class="icon" src="../shared_images/vertical_align_bottom_light.png"> vertical_align_bottom.</p>
index ef967d1d59138ecc150de09009eeb4119df8cc42..4e01d4fa8d3e926f6bbb6532f3e9e21fe62d36c4 100644 (file)
@@ -52,6 +52,7 @@
         <p>Privacy Browser è compilato utilizzando le <a href="https://developer.android.com/jetpack/androidx/">Librerie AndroidX</a>
             e il codice disponibile nella <a href="https://mvnrepository.com/artifact/com.google.android.material/material">Google Material Maven repository</a>,
             entrambi rilasciati con <a href="https://www.apache.org/licenses/LICENSE-2.0">Licenza Apache 2.0</a>.</p>
+
         <p>La versione gratuita di Privacy Browser è compilata con <a href="https://mvnrepository.com/artifact/com.google.firebase/firebase-ads">Firebase Ads</a>,
             che è rilasciato sotto la <a href="https://developer.android.com/studio/terms">Licenza Android Software Development Kit</a>.</p>
 
         <p><img class="icon" src="../shared_images/sort_dark.png"> sort.</p>
         <p><img class="icon" src="../shared_images/style_dark.png"> style.</p>
         <p><img class="icon" src="../shared_images/subtitles_dark.png"> subtitles.</p>
+        <p><img class="icon" src="../shared_images/tab_dark.png"> tab.</p>
         <p><img class="icon" src="../shared_images/text_fields_dark.png"> text_fields.</p>
         <p><img class="icon" src="../shared_images/thumbs_up_down_dark.png"> thumbs_up_down.</p>
         <p><img class="icon" src="../shared_images/vertical_align_bottom_dark.png"> vertical_align_bottom.</p>
index 361155ad6ae526bc6c662467bb134f7077a51aef..320d866835b1287d978cb6afe4ee28a1034ac380 100644 (file)
@@ -52,6 +52,7 @@
         <p>Privacy Browser è compilato utilizzando le <a href="https://developer.android.com/jetpack/androidx/">Librerie AndroidX</a>
             e il codice disponibile nella <a href="https://mvnrepository.com/artifact/com.google.android.material/material">Google Material Maven repository</a>,
             entrambi rilasciati con <a href="https://www.apache.org/licenses/LICENSE-2.0">Licenza Apache 2.0</a>.</p>
+
         <p>La versione gratuita di Privacy Browser è compilata con <a href="https://mvnrepository.com/artifact/com.google.firebase/firebase-ads">Firebase Ads</a>,
             che è rilasciato sotto la <a href="https://developer.android.com/studio/terms">Licenza Android Software Development Kit</a>.</p>
 
         <p><img class="icon" src="../shared_images/sort_light.png"> sort.</p>
         <p><img class="icon" src="../shared_images/style_light.png"> style.</p>
         <p><img class="icon" src="../shared_images/subtitles_light.png"> subtitles.</p>
+        <p><img class="icon" src="../shared_images/tab_dark.png"> tab.</p>
         <p><img class="icon" src="../shared_images/text_fields_light.png"> text_fields.</p>
         <p><img class="icon" src="../shared_images/thumbs_up_down_light.png"> thumbs_up_down.</p>
         <p><img class="icon" src="../shared_images/vertical_align_bottom_light.png"> vertical_align_bottom.</p>
index 9b60e92c0fe942f06b99fe8b133814eefb812db2..1ca17ea5d640f36f2b84f984e31c3c8aeb6084a6 100644 (file)
@@ -47,6 +47,7 @@
         <p>Privacy Browser создан на базе библиотек <a href="https://developer.android.com/jetpack/androidx/">AndroidX</a>
             и кодовой базы из репозитория <a href="https://mvnrepository.com/artifact/com.google.android.material/material">Google Material Maven</a>,
             которые выпущены под лицензией <a href="https://www.apache.org/licenses/LICENSE-2.0">Apache 2.0</a>.</p>
+
         <p>Бесплатный вариант Privacy Browser создан с помощью <a href="https://mvnrepository.com/artifact/com.google.firebase/firebase-ads">Firebase Ads</a>,
             выпущенной по лицензии <a href="https://developer.android.com/studio/terms">Android Software Development Kit License</a>.</p>
 
         <p><img class="icon" src="../shared_images/sort_dark.png"> sort.</p>
         <p><img class="icon" src="../shared_images/style_dark.png"> style.</p>
         <p><img class="icon" src="../shared_images/subtitles_dark.png"> subtitles.</p>
+        <p><img class="icon" src="../shared_images/tab_dark.png"> tab.</p>
         <p><img class="icon" src="../shared_images/text_fields_dark.png"> text_fields.</p>
         <p><img class="icon" src="../shared_images/thumbs_up_down_dark.png"> thumbs_up_down.</p>
         <p><img class="icon" src="../shared_images/vertical_align_bottom_dark.png"> vertical_align_bottom.</p>
index 373b61f2ea3834642c990b73482c5cf7b72d5976..7f90a11b9e25ab1b942c1d7883585dbfae450cab 100644 (file)
@@ -47,6 +47,7 @@
         <p>Privacy Browser создан на базе библиотек <a href="https://developer.android.com/jetpack/androidx/">AndroidX</a>
             и кодовой базы из репозитория <a href="https://mvnrepository.com/artifact/com.google.android.material/material">Google Material Maven</a>,
             которые выпущены под лицензией <a href="https://www.apache.org/licenses/LICENSE-2.0">Apache 2.0</a>.</p>
+
         <p>Бесплатный вариант Privacy Browser создан с помощью <a href="https://mvnrepository.com/artifact/com.google.firebase/firebase-ads">Firebase Ads</a>,
             выпущенной по лицензии <a href="https://developer.android.com/studio/terms">Android Software Development Kit License</a>.</p>
 
         <p><img class="icon" src="../shared_images/sort_light.png"> sort.</p>
         <p><img class="icon" src="../shared_images/style_light.png"> style.</p>
         <p><img class="icon" src="../shared_images/subtitles_light.png"> subtitles.</p>
+        <p><img class="icon" src="../shared_images/tab_dark.png"> tab.</p>
         <p><img class="icon" src="../shared_images/text_fields_light.png"> text_fields.</p>
         <p><img class="icon" src="../shared_images/thumbs_up_down_light.png"> thumbs_up_down.</p>
         <p><img class="icon" src="../shared_images/vertical_align_bottom_light.png"> vertical_align_bottom.</p>
diff --git a/app/src/main/assets/shared_images/tab_dark.png b/app/src/main/assets/shared_images/tab_dark.png
new file mode 100644 (file)
index 0000000..1509a09
Binary files /dev/null and b/app/src/main/assets/shared_images/tab_dark.png differ
diff --git a/app/src/main/assets/shared_images/tab_light.png b/app/src/main/assets/shared_images/tab_light.png
new file mode 100644 (file)
index 0000000..39c05d4
Binary files /dev/null and b/app/src/main/assets/shared_images/tab_light.png differ
index c852efefbca7e711c2477aea197fbc68fc54842e..627b822bf0856087db6357dfb6439adcaabdfa0a 100644 (file)
@@ -47,6 +47,7 @@
         <p>Privacy Browser is built with the <a href="https://developer.android.com/jetpack/androidx/">AndroidX Libraries</a>
             and code from the <a href="https://mvnrepository.com/artifact/com.google.android.material/material">Google Material Maven repository</a>,
             which are released under the <a href="https://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>.</p>
+
         <p>The free flavor of Privacy Browser is built with <a href="https://mvnrepository.com/artifact/com.google.firebase/firebase-ads">Firebase Ads</a>,
             which is released under the <a href="https://developer.android.com/studio/terms">Android Software Development Kit License</a>.</p>
 
         <p><img class="icon" src="../shared_images/sort_dark.png"> sort.</p>
         <p><img class="icon" src="../shared_images/style_dark.png"> style.</p>
         <p><img class="icon" src="../shared_images/subtitles_dark.png"> subtitles.</p>
+        <p><img class="icon" src="../shared_images/tab_dark.png"> tab.</p>
         <p><img class="icon" src="../shared_images/text_fields_dark.png"> text_fields.</p>
         <p><img class="icon" src="../shared_images/thumbs_up_down_dark.png"> thumbs_up_down.</p>
         <p><img class="icon" src="../shared_images/vertical_align_bottom_dark.png"> vertical_align_bottom.</p>
index 6bc97ec97afff386150e216ce4415ea65bc8a2d6..8876b9dcad499e407d8d83fa314f25460463b4ed 100644 (file)
@@ -47,6 +47,7 @@
         <p>Privacy Browser is built with the <a href="https://developer.android.com/jetpack/androidx/">AndroidX Libraries</a>
             and code from the <a href="https://mvnrepository.com/artifact/com.google.android.material/material">Google Material Maven repository</a>,
             which are released under the <a href="https://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>.</p>
+
         <p>The free flavor of Privacy Browser is built with <a href="https://mvnrepository.com/artifact/com.google.firebase/firebase-ads">Firebase Ads</a>,
             which is released under the <a href="https://developer.android.com/studio/terms">Android Software Development Kit License</a>.</p>
 
         <p><img class="icon" src="../shared_images/sort_light.png"> sort.</p>
         <p><img class="icon" src="../shared_images/style_light.png"> style.</p>
         <p><img class="icon" src="../shared_images/subtitles_light.png"> subtitles.</p>
+        <p><img class="icon" src="../shared_images/tab_dark.png"> tab.</p>
         <p><img class="icon" src="../shared_images/text_fields_light.png"> text_fields.</p>
         <p><img class="icon" src="../shared_images/thumbs_up_down_light.png"> thumbs_up_down.</p>
         <p><img class="icon" src="../shared_images/vertical_align_bottom_light.png"> vertical_align_bottom.</p>
index d84b9c49f1b536e66ede32b67216ce7e142ed6a1..3508b7521f3de43efec13ac42c3ca111abaabaa8 100644 (file)
@@ -56,8 +56,12 @@ public class AboutActivity extends AppCompatActivity {
         // Set the content view.
         setContentView(R.layout.about_coordinatorlayout);
 
-        // `SupportActionBar` from `android.support.v7.app.ActionBar` must be used until the minimum API is >= 21.
+        // Get handles for the views.
         Toolbar toolbar = findViewById(R.id.about_toolbar);
+        TabLayout aboutTabLayout = findViewById(R.id.about_tablayout);
+        ViewPager aboutViewPager = findViewById(R.id.about_viewpager);
+
+        // Set the action bar.  `SupportActionBar` must be used until the minimum API is >= 21.
         setSupportActionBar(toolbar);
 
         // Get a handle for the action bar.
@@ -70,16 +74,14 @@ public class AboutActivity extends AppCompatActivity {
         actionBar.setDisplayHomeAsUpEnabled(true);
 
         //  Setup the ViewPager.
-        ViewPager aboutViewPager = findViewById(R.id.about_viewpager);
-        aboutViewPager.setAdapter(new aboutPagerAdapter(getSupportFragmentManager()));
+        aboutViewPager.setAdapter(new AboutPagerAdapter(getSupportFragmentManager()));
 
-        // Setup the TabLayout and connect it to the ViewPager.
-        TabLayout aboutTabLayout = findViewById(R.id.about_tablayout);
+        // Connect the tab layout to the view pager.
         aboutTabLayout.setupWithViewPager(aboutViewPager);
     }
 
-    private class aboutPagerAdapter extends FragmentPagerAdapter {
-        private aboutPagerAdapter(FragmentManager fragmentManager) {
+    private class AboutPagerAdapter extends FragmentPagerAdapter {
+        private AboutPagerAdapter(FragmentManager fragmentManager) {
             // Run the default commands.
             super(fragmentManager);
         }
index 360f858d4c0d4de543033fd1fe4721b637bfedb0..d635cbc6a0b6d5296bd7dee083a78127f5e61361 100644 (file)
@@ -24,14 +24,11 @@ import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
 import android.database.Cursor;
-import android.database.CursorWindow;
-import android.database.sqlite.SQLiteCursor;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Typeface;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.os.Bundle;
 import android.util.SparseBooleanArray;
 import android.view.ActionMode;
@@ -93,8 +90,7 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma
 
     // `bookmarksCursor` is used in `onCreate()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`, `deleteBookmarkFolderContents()`,
     // `loadFolder()`, and `onDestroy()`.
-    // TODO This should be switched back to a `Cursor` after the release of 2.17.1.
-    private SQLiteCursor bookmarksCursor;
+    private Cursor bookmarksCursor;
 
     // `bookmarksCursorAdapter` is used in `onCreate(), `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveBookmark()`, `onSaveBookmarkFolder()`, `onMoveToFolder()`, and `onLoadFolder()`.
     private CursorAdapter bookmarksCursorAdapter;
@@ -338,8 +334,7 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma
                         }
 
                         // Update the bookmarks cursor with the current contents of the bookmarks database.
-                        // TODO Change this back to a `Cursor` after 2.17.1 is released.
-                        bookmarksCursor = (SQLiteCursor) bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
+                        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
 
                         // Update the `ListView`.
                         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
@@ -385,8 +380,7 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma
                         }
 
                         // Update the bookmarks cursor with the current contents of the bookmarks database.
-                        // TODO Change this back to a `Cursor` after 2.17.1 is released.
-                        bookmarksCursor = (SQLiteCursor) bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
+                        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
 
                         // Update the `ListView`.
                         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
@@ -447,8 +441,7 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma
                         selectedBookmarksPositionsSparseBooleanArray = bookmarksListView.getCheckedItemPositions().clone();
 
                         // Update the bookmarks cursor with the current contents of the bookmarks database except for the specified database IDs.
-                        // TODO Change this back to a `Cursor` after 2.17.1 is released.
-                        bookmarksCursor = (SQLiteCursor) bookmarksDatabaseHelper.getBookmarksByDisplayOrderExcept(selectedBookmarksIdsLongArray, currentFolder);
+                        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrderExcept(selectedBookmarksIdsLongArray, currentFolder);
 
                         // Update the list view.
                         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
@@ -467,8 +460,7 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma
                                             // The user pushed the `Undo` button.
                                             case Snackbar.Callback.DISMISS_EVENT_ACTION:
                                                 // Update the bookmarks cursor with the current contents of the bookmarks database, including the "deleted" bookmarks.
-                                                // TODO Change this back to a `Cursor` after 2.17.1 is released.
-                                                bookmarksCursor = (SQLiteCursor) bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
+                                                bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
 
                                                 // Update the list view.
                                                 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
@@ -681,8 +673,7 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma
         bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
 
         // Update the bookmarks cursor with the current contents of this folder.
-        // TODO Change this back to a `Cursor` after 2.17.1 is released.
-        bookmarksCursor = (SQLiteCursor) bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
+        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
 
         // Update the `ListView`.
         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
@@ -743,8 +734,7 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma
         bookmarksDatabaseHelper.createFolder(folderNameString, currentFolder, folderIconByteArray);
 
         // Update the bookmarks cursor with the current contents of this folder.
-        // TODO Change this back to a `Cursor` after 2.17.1 is released.
-        bookmarksCursor = (SQLiteCursor) bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
+        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
 
         // Update the `ListView`.
         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
@@ -793,8 +783,7 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma
         contextualActionMode.finish();
 
         // Update the bookmarks cursor with the contents of the current folder.
-        // TODO Change this back to a `Cursor` after 2.17.1 is released.
-        bookmarksCursor = (SQLiteCursor) bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
+        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
 
         // Update the `ListView`.
         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
@@ -888,8 +877,7 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma
         }
 
         // Update the bookmarks cursor with the current contents of this folder.
-        // TODO Change this back to a `Cursor` after 2.17.1 is released.
-        bookmarksCursor = (SQLiteCursor) bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
+        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
 
         // Update the `ListView`.
         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
@@ -934,8 +922,7 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma
         }
 
         // Update the bookmarks cursor with the current contents of this folder.
-        // TODO Change this back to a `Cursor` after 2.17.1 is released.
-        bookmarksCursor = (SQLiteCursor) bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
+        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
 
         // Update the `ListView`.
         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
@@ -1039,16 +1026,7 @@ public class BookmarksActivity extends AppCompatActivity implements CreateBookma
 
     private void loadFolder() {
         // Update bookmarks cursor with the contents of the bookmarks database for the current folder.
-        // TODO Change this back to a `Cursor` after 2.17.1 is released.
-        bookmarksCursor = (SQLiteCursor) bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
-
-        // TODO Remove after the release of 2.17.1.
-        if (Build.VERSION.SDK_INT >= 28) {
-            // Create a big cursor window.
-            CursorWindow bigCursorWindow = new CursorWindow("Big Cursor Window", 4194304);
-
-            bookmarksCursor.setWindow(bigCursorWindow);
-        }
+        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentFolder);
 
         // Setup a `CursorAdapter`.  `this` specifies the `Context`.  `false` disables `autoRequery`.
         bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
index c6e065b18391a39bbd92e453f30640a9f3d4f405..a174230d851af1cd9444184b5769d182a51f81ca 100644 (file)
@@ -103,13 +103,17 @@ import androidx.core.content.ContextCompat;
 import androidx.core.view.GravityCompat;
 import androidx.drawerlayout.widget.DrawerLayout;
 import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
 import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentPagerAdapter;
 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
+import androidx.viewpager.widget.ViewPager;
 
 import com.google.android.material.floatingactionbutton.FloatingActionButton;
 import com.google.android.material.navigation.NavigationView;
 import com.google.android.material.snackbar.Snackbar;
 
+import com.google.android.material.tabs.TabLayout;
 import com.stoutner.privacybrowser.BuildConfig;
 import com.stoutner.privacybrowser.R;
 import com.stoutner.privacybrowser.dialogs.AdConsentDialog;
@@ -124,6 +128,7 @@ import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog;
 import com.stoutner.privacybrowser.dialogs.PinnedMismatchDialog;
 import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
 import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
+import com.stoutner.privacybrowser.fragments.WebViewTabFragment;
 import com.stoutner.privacybrowser.helpers.AdHelper;
 import com.stoutner.privacybrowser.helpers.BlockListHelper;
 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
@@ -149,6 +154,7 @@ import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -156,8 +162,8 @@ import java.util.Set;
 // AppCompatActivity from android.support.v7.app.AppCompatActivity must be used to have access to the SupportActionBar until the minimum API is >= 21.
 public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
         DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener, DownloadLocationPermissionDialog.DownloadLocationPermissionDialogListener, EditBookmarkDialog.EditBookmarkListener,
-        EditBookmarkFolderDialog.EditBookmarkFolderListener, HttpAuthenticationDialog.HttpAuthenticationListener, NavigationView.OnNavigationItemSelectedListener, PinnedMismatchDialog.PinnedMismatchListener,
-        SslCertificateErrorDialog.SslCertificateErrorListener, UrlHistoryDialog.UrlHistoryListener {
+        EditBookmarkFolderDialog.EditBookmarkFolderListener, HttpAuthenticationDialog.HttpAuthenticationListener, NavigationView.OnNavigationItemSelectedListener, WebViewTabFragment.NewTabListener,
+        PinnedMismatchDialog.PinnedMismatchListener, SslCertificateErrorDialog.SslCertificateErrorListener, UrlHistoryDialog.UrlHistoryListener {
 
     // `darkTheme` is public static so it can be accessed from everywhere.
     public static boolean darkTheme;
@@ -309,22 +315,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     private static FragmentManager fragmentManager;
 
 
+    // A handle for the activity is set in `onCreate()` and accessed in `WebViewPagerAdapter`.
+    private Activity activity;
+
     // `navigatingHistory` is used in `onCreate()`, `onNavigationItemSelected()`, `onSslMismatchBack()`, and `applyDomainSettings()`.
     private boolean navigatingHistory;
 
-    // `mainWebView` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
+    // The current WebView is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
     // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, and `applyProxyThroughOrbot()`.
-    private NestedScrollWebView mainWebView;
+    private NestedScrollWebView currentWebView;
 
     // `fullScreenVideoFrameLayout` is used in `onCreate()` and `onConfigurationChanged()`.
     private FrameLayout fullScreenVideoFrameLayout;
 
-    // `urlAppBarRelativeLayout` is used in `onCreate()` and `applyDomainSettings()`.
-    private RelativeLayout urlAppBarRelativeLayout;
-
-    // `favoriteIconImageView` is used in `onCreate()` and `applyDomainSettings()`
-    private ImageView favoriteIconImageView;
-
     // `cookieManager` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`, `loadUrlFromTextBox()`, `onDownloadImage()`, `onDownloadFile()`, and `onRestart()`.
     private CookieManager cookieManager;
 
@@ -361,6 +364,22 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `refreshMenuItem` is used in `onCreate()` and `onCreateOptionsMenu()`.
     private MenuItem refreshMenuItem;
 
+    // The WebView pager adapter is used in `onCreate()`, `onResume()`, and `addTab()`.
+    private WebViewPagerAdapter webViewPagerAdapter;
+
+    // The navigation requests menu item is used in `onCreate()` and accessed from `WebViewPagerAdapter`.
+    private MenuItem navigationRequestsMenuItem;
+
+    // The blocklist helper is used in `onCreate()` and `WebViewPagerAdapter`.
+    BlockListHelper blockListHelper;
+
+    // The blocklists are populated in `onCreate()` and accessed from `WebViewPagerAdapter`.
+    private ArrayList<List<String[]>> easyList;
+    private ArrayList<List<String[]>> easyPrivacy;
+    private ArrayList<List<String[]>> fanboysAnnoyanceList;
+    private ArrayList<List<String[]>> fanboysSocialList;
+    private ArrayList<List<String[]>> ultraPrivacy;
+
     // The blocklist menu items are used in `onCreate()`, `onCreateOptionsMenu()`, and `onPrepareOptionsMenu()`.
     private MenuItem blocklistsMenuItem;
     private MenuItem easyListMenuItem;
@@ -487,7 +506,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `fileChooserCallback` is used in `onCreate()` and `onActivityResult()`.
     private ValueCallback<Uri[]> fileChooserCallback;
 
-    // The download strings are used in `onCreate()` and `onRequestPermissionResult()`.
+    // The download strings are used in `onCreate()`, `onRequestPermissionResult()` and `initializeWebView()`.
     private String downloadUrl;
     private String downloadContentDisposition;
     private long downloadContentLength;
@@ -499,7 +518,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     private ArrayAdapter<CharSequence> userAgentNamesArray;
     private String[] userAgentDataArray;
 
-    // The request codes are used in `onCreate()`, `onCreateContextMenu()`, `onCloseDownloadLocationPermissionDialog()`, and `onRequestPermissionResult()`.
+    // The request codes are used in `onCreate()`, `onCreateContextMenu()`, `onCloseDownloadLocationPermissionDialog()`, `onRequestPermissionResult()`, and `initializeWebView()`.
     private final int DOWNLOAD_FILE_REQUEST_CODE = 1;
     private final int DOWNLOAD_IMAGE_REQUEST_CODE = 2;
 
@@ -536,6 +555,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         setContentView(R.layout.main_framelayout);
 
         // Get handles for views, resources, and managers.
+        activity = this;
         Resources resources = getResources();
         fragmentManager = getSupportFragmentManager();
         inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
@@ -620,12 +640,32 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Register `orbotStatusBroadcastReceiver` on `this` context.
         this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS"));
 
+        // Instantiate the block list helper.
+        blockListHelper = new BlockListHelper();
+
+        // Initialize the list of resource requests.
+        resourceRequests = new ArrayList<>();
+
+        // Parse the block lists.
+        easyList = blockListHelper.parseBlockList(getAssets(), "blocklists/easylist.txt");
+        easyPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/easyprivacy.txt");
+        fanboysAnnoyanceList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-annoyance.txt");
+        fanboysSocialList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-social.txt");
+        ultraPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/ultraprivacy.txt");
+
+        // Store the list versions.
+        easyListVersion = easyList.get(0).get(0)[0];
+        easyPrivacyVersion = easyPrivacy.get(0).get(0)[0];
+        fanboysAnnoyanceVersion = fanboysAnnoyanceList.get(0).get(0)[0];
+        fanboysSocialVersion = fanboysSocialList.get(0).get(0)[0];
+        ultraPrivacyVersion = ultraPrivacy.get(0).get(0)[0];
+
         // Get handles for views that need to be modified.
-        FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
         DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
-        RelativeLayout mainContentRelativeLayout = findViewById(R.id.main_content_relativelayout);
+        final NavigationView navigationView = findViewById(R.id.navigationview);
+        TabLayout tabLayout = findViewById(R.id.tablayout);
         SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
-        mainWebView = findViewById(R.id.main_webview);
+        ViewPager webViewPager = findViewById(R.id.webviewpager);
         bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
         bookmarksTitleTextView = findViewById(R.id.bookmarks_title_textview);
         FloatingActionButton launchBookmarksActivityFab = findViewById(R.id.launch_bookmarks_activity_fab);
@@ -633,8 +673,87 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         FloatingActionButton createBookmarkFab = findViewById(R.id.create_bookmark_fab);
         findOnPageEditText = findViewById(R.id.find_on_page_edittext);
         fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
-        urlAppBarRelativeLayout = findViewById(R.id.url_app_bar_relativelayout);
-        favoriteIconImageView = findViewById(R.id.favorite_icon);
+
+        // Listen for touches on the navigation menu.
+        navigationView.setNavigationItemSelectedListener(this);
+
+        // Get handles for the navigation menu and the back and forward menu items.  The menu is zero-based.
+        final Menu navigationMenu = navigationView.getMenu();
+        final MenuItem navigationCloseTabMenuItem = navigationMenu.getItem(0);
+        final MenuItem navigationBackMenuItem = navigationMenu.getItem(3);
+        final MenuItem navigationForwardMenuItem = navigationMenu.getItem(4);
+        final MenuItem navigationHistoryMenuItem = navigationMenu.getItem(5);
+        navigationRequestsMenuItem = navigationMenu.getItem(6);
+
+        // Initialize the web view pager adapter.
+        webViewPagerAdapter = new WebViewPagerAdapter(fragmentManager);
+
+        // Set the pager adapter on the web view pager.
+        webViewPager.setAdapter(webViewPagerAdapter);
+
+        // Store up to 100 tabs in memory.
+        webViewPager.setOffscreenPageLimit(100);
+
+        // Update the web view pager every time a tab is modified.
+        webViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
+            @Override
+            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+                // Do nothing.
+            }
+
+            @Override
+            public void onPageSelected(int position) {
+                // TODO.  Consider using an array of the WebViews.
+                // Get the current WebView fragment.  Instantiate item returns the current item if it already exists.
+                Fragment webViewFragment = (Fragment) webViewPagerAdapter.instantiateItem(webViewPager, position);
+
+                // Store the current WebView.
+                currentWebView = (NestedScrollWebView) webViewFragment.getView();
+
+                // Select the corresponding tab if it does not match the currently selected page.  This will happen if the page was scrolled via swiping in the view pager.
+                if (tabLayout.getSelectedTabPosition() != position) {
+                    // Get a handle for the corresponding tab.
+                    TabLayout.Tab correspondingTab = tabLayout.getTabAt(position);
+
+                    // Assert that the corresponding tab is not null.
+                    assert correspondingTab != null;
+
+                    // Select the corresponding tab.
+                    correspondingTab.select();
+                }
+            }
+
+            @Override
+            public void onPageScrollStateChanged(int state) {
+                // Do nothing.
+            }
+        });
+
+        // Display the View SSL Certificate dialog when the currently selected tab is reselected.
+        tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
+            @Override
+            public void onTabSelected(TabLayout.Tab tab) {
+                // Select the same page in the view pager.
+                webViewPager.setCurrentItem(tab.getPosition());
+            }
+
+            @Override
+            public void onTabUnselected(TabLayout.Tab tab) {
+                // Do nothing.
+            }
+
+            @Override
+            public void onTabReselected(TabLayout.Tab tab) {
+                // Instantiate the View SSL Certificate dialog.
+                DialogFragment viewSslCertificateDialogFragment = new ViewSslCertificateDialog();
+
+                // Display the View SSL Certificate dialog.
+                viewSslCertificateDialogFragment.show(getSupportFragmentManager(), getString(R.string.view_ssl_certificate));
+            }
+        });
+
+        // Add the first tab.
+        webViewPagerAdapter.addPage();
 
         // Set the bookmarks drawer resources according to the theme.  This can't be done in the layout due to compatibility issues with the `DrawerLayout` support widget.
         if (darkTheme) {
@@ -675,95 +794,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             createBookmarkDialog.show(fragmentManager, resources.getString(R.string.create_bookmark));
         });
 
-        // Create a double-tap listener to toggle full-screen mode.
-        final GestureDetector gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
-            // Override `onDoubleTap()`.  All other events are handled using the default settings.
-            @Override
-            public boolean onDoubleTap(MotionEvent event) {
-                if (fullScreenBrowsingModeEnabled) {  // Only process the double-tap if full screen browsing mode is enabled.
-                    // Toggle the full screen browsing mode tracker.
-                    inFullScreenBrowsingMode = !inFullScreenBrowsingMode;
-
-                    // Toggle the full screen browsing mode.
-                    if (inFullScreenBrowsingMode) {  // Switch to full screen mode.
-                        // Hide the app bar if specified.
-                        if (hideAppBar) {
-                            actionBar.hide();
-                        }
-
-                        // Hide the banner ad in the free flavor.
-                        if (BuildConfig.FLAVOR.contentEquals("free")) {
-                            AdHelper.hideAd(findViewById(R.id.adview));
-                        }
-
-                        // Remove the translucent status flag.  This is necessary so the root frame layout can fill the entire screen.
-                        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
-
-                        /* Hide the system bars.
-                         * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
-                         * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
-                         * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
-                         * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
-                         */
-                        rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
-                                View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
-                    } else {  // Switch to normal viewing mode.
-                        // Show the app bar.
-                        actionBar.show();
-
-                        // Show the banner ad in the free flavor.
-                        if (BuildConfig.FLAVOR.contentEquals("free")) {
-                            // Reload the ad.
-                            AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
-                        }
-
-                        // Remove the `SYSTEM_UI` flags from the root frame layout.
-                        rootFrameLayout.setSystemUiVisibility(0);
-
-                        // Add the translucent status flag.
-                        getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
-                    }
-
-                    // Consume the double-tap.
-                    return true;
-                } else { // Do not consume the double-tap because full screen browsing mode is disabled.
-                    return false;
-                }
-            }
-        });
-
-        // Pass all touch events on `mainWebView` through `gestureDetector` to check for double-taps.
-        mainWebView.setOnTouchListener((View view, MotionEvent event) -> {
-            // Call `performClick()` on the view, which is required for accessibility.
-            view.performClick();
-
-            // Send the `event` to `gestureDetector`.
-            return gestureDetector.onTouchEvent(event);
-        });
-
-        // Update `findOnPageCountTextView`.
-        mainWebView.setFindListener(new WebView.FindListener() {
-            // Get a handle for `findOnPageCountTextView`.
-            final TextView findOnPageCountTextView = findViewById(R.id.find_on_page_count_textview);
-
-            @Override
-            public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
-                if ((isDoneCounting) && (numberOfMatches == 0)) {  // There are no matches.
-                    // Set `findOnPageCountTextView` to `0/0`.
-                    findOnPageCountTextView.setText(R.string.zero_of_zero);
-                } else if (isDoneCounting) {  // There are matches.
-                    // `activeMatchOrdinal` is zero-based.
-                    int activeMatch = activeMatchOrdinal + 1;
-
-                    // Build the match string.
-                    String matchString = activeMatch + "/" + numberOfMatches;
-
-                    // Set `findOnPageCountTextView`.
-                    findOnPageCountTextView.setText(matchString);
-                }
-            }
-        });
-
         // Search for the string on the page whenever a character changes in the `findOnPageEditText`.
         findOnPageEditText.addTextChangedListener(new TextWatcher() {
             @Override
@@ -779,7 +809,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             @Override
             public void afterTextChanged(Editable s) {
                 // Search for the text in `mainWebView`.
-                mainWebView.findAllAsync(findOnPageEditText.getText().toString());
+                currentWebView.findAllAsync(findOnPageEditText.getText().toString());
             }
         });
 
@@ -787,7 +817,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         findOnPageEditText.setOnKeyListener((v, keyCode, event) -> {
             if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {  // The `enter` key was pressed.
                 // Hide the soft keyboard.  `0` indicates no additional flags.
-                inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
+                inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
 
                 // Consume the event.
                 return true;
@@ -798,7 +828,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         });
 
         // Implement swipe to refresh.
-        swipeRefreshLayout.setOnRefreshListener(() -> mainWebView.reload());
+        swipeRefreshLayout.setOnRefreshListener(() -> currentWebView.reload());
 
         // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
         swipeRefreshLayout.setProgressViewOffset(false, swipeRefreshLayout.getProgressViewStartOffset() - 10, swipeRefreshLayout.getProgressViewEndOffset());
@@ -815,17 +845,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
         drawerLayout.setDrawerTitle(GravityCompat.END, getString(R.string.bookmarks));
 
-        // Listen for touches on the navigation menu.
-        final NavigationView navigationView = findViewById(R.id.navigationview);
-        navigationView.setNavigationItemSelectedListener(this);
-
-        // Get handles for `navigationMenu` and the back and forward menu items.  The menu is zero-based, so items 1, 2, and 3 are the second, third, and fourth entries in the menu.
-        final Menu navigationMenu = navigationView.getMenu();
-        final MenuItem navigationBackMenuItem = navigationMenu.getItem(1);
-        final MenuItem navigationForwardMenuItem = navigationMenu.getItem(2);
-        final MenuItem navigationHistoryMenuItem = navigationMenu.getItem(3);
-        final MenuItem navigationRequestsMenuItem = navigationMenu.getItem(4);
-
         // Initialize the bookmarks database helper.  The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
         bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0);
 
@@ -929,18 +948,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         bookmarksHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
                     }
 
-                    // Update the back, forward, history, and requests menu items.
-                    navigationBackMenuItem.setEnabled(mainWebView.canGoBack());
-                    navigationForwardMenuItem.setEnabled(mainWebView.canGoForward());
-                    navigationHistoryMenuItem.setEnabled((mainWebView.canGoBack() || mainWebView.canGoForward()));
+                    // Update the navigation menu items.
+                    navigationCloseTabMenuItem.setEnabled(tabLayout.getTabCount() > 1);
+                    navigationBackMenuItem.setEnabled(currentWebView.canGoBack());
+                    navigationForwardMenuItem.setEnabled(currentWebView.canGoForward());
+                    navigationHistoryMenuItem.setEnabled((currentWebView.canGoBack() || currentWebView.canGoForward()));
                     navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
 
                     // Hide the keyboard (if displayed).
-                    inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
+                    inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
 
                     // Clear the focus from from the URL text box and the WebView.  This removes any text selection markers and context menus, which otherwise draw above the open drawers.
                     urlTextBox.clearFocus();
-                    mainWebView.clearFocus();
+                    currentWebView.clearFocus();
                 }
             }
         });
@@ -948,4112 +968,4390 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Create the hamburger icon at the start of the AppBar.
         actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
 
-        // Get a handle for the progress bar.
-        final ProgressBar progressBar = findViewById(R.id.progress_bar);
+        // Initialize cookieManager.
+        cookieManager = CookieManager.getInstance();
 
-        mainWebView.setWebChromeClient(new WebChromeClient() {
-            // Update the progress bar when a page is loading.
-            @Override
-            public void onProgressChanged(WebView view, int progress) {
-                // Inject the night mode CSS if night mode is enabled.
-                if (nightMode) {
-                    // `background-color: #212121` sets the background to be dark gray.  `color: #BDBDBD` sets the text color to be light gray.  `box-shadow: none` removes a lower underline on links
-                    // used by WordPress.  `text-decoration: none` removes all text underlines.  `text-shadow: none` removes text shadows, which usually have a hard coded color.
-                    // `border: none` removes all borders, which can also be used to underline text.  `a {color: #1565C0}` sets links to be a dark blue.
-                    // `::selection {background: #0D47A1}' sets the text selection highlight color to be a dark blue. `!important` takes precedent over any existing sub-settings.
-                    mainWebView.evaluateJavascript("(function() {var parent = document.getElementsByTagName('head').item(0); var style = document.createElement('style'); style.type = 'text/css'; " +
-                            "style.innerHTML = '* {background-color: #212121 !important; color: #BDBDBD !important; box-shadow: none !important; text-decoration: none !important;" +
-                            "text-shadow: none !important; border: none !important;} a {color: #1565C0 !important;} ::selection {background: #0D47A1 !important;}'; parent.appendChild(style)})()", value -> {
-                                // Initialize a handler to display `mainWebView`.
-                                Handler displayWebViewHandler = new Handler();
-
-                                // Setup a runnable to display `mainWebView` after a delay to allow the CSS to be applied.
-                                Runnable displayWebViewRunnable = () -> {
-                                    // Only display `mainWebView` if the progress bar is gone.  This prevents the display of the `WebView` while it is still loading.
-                                    if (progressBar.getVisibility() == View.GONE) {
-                                        mainWebView.setVisibility(View.VISIBLE);
-                                    }
-                                };
-
-                                // Displaying of `mainWebView` after 500 milliseconds.
-                                displayWebViewHandler.postDelayed(displayWebViewRunnable, 500);
-                            });
-                }
+        // Replace the header that `WebView` creates for `X-Requested-With` with a null value.  The default value is the application ID (com.stoutner.privacybrowser.standard).
+        customHeaders.put("X-Requested-With", "");
 
-                // Update the progress bar.
-                progressBar.setProgress(progress);
+        // Initialize the default preference values the first time the program is run.  `false` keeps this command from resetting any current preferences back to default.
+        PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
 
-                // Set the visibility of the progress bar.
-                if (progress < 100) {
-                    // Show the progress bar.
-                    progressBar.setVisibility(View.VISIBLE);
-                } else {
-                    // Hide the progress bar.
-                    progressBar.setVisibility(View.GONE);
+        // Get a handle for the `Runtime`.
+        privacyBrowserRuntime = Runtime.getRuntime();
 
-                    // Display `mainWebView` if night mode is disabled.
-                    // Because of a race condition between `applyDomainSettings` and `onPageStarted`, when night mode is set by domain settings the `WebView` may be hidden even if night mode is not
-                    // currently enabled.
-                    if (!nightMode) {
-                        mainWebView.setVisibility(View.VISIBLE);
-                    }
+        // Store the application's private data directory.
+        privateDataDirectoryString = getApplicationInfo().dataDir;
+        // `dataDir` will vary, but will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, which links to `/data/data/com.stoutner.privacybrowser.standard`.
 
-                    //Stop the swipe to refresh indicator if it is running
-                    swipeRefreshLayout.setRefreshing(false);
-                }
-            }
+        // Initialize `inFullScreenBrowsingMode`, which is always false at this point because Privacy Browser never starts in full screen browsing mode.
+        inFullScreenBrowsingMode = false;
 
-            // Set the favorite icon when it changes.
-            @Override
-            public void onReceivedIcon(WebView view, Bitmap icon) {
-                // Only update the favorite icon if the website has finished loading.
-                if (progressBar.getVisibility() == View.GONE) {
-                    // Save a copy of the favorite icon.
-                    favoriteIconBitmap = icon;
+        // Initialize the privacy settings variables.
+        javaScriptEnabled = false;
+        firstPartyCookiesEnabled = false;
+        thirdPartyCookiesEnabled = false;
+        domStorageEnabled = false;
+        saveFormDataEnabled = false;  // Form data can be removed once the minimum API >= 26.
+        nightMode = false;
 
-                    // Place the favorite icon in the appBar.
-                    favoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true));
-                }
-            }
+        // Store the default user agent.
+        // TODO webViewDefaultUserAgent = mainWebView.getSettings().getUserAgentString();
 
-            // Save a copy of the title when it changes.
-            @Override
-            public void onReceivedTitle(WebView view, String title) {
-                // Save a copy of the title.
-                webViewTitle = title;
-            }
+        // Initialize the WebView title.
+        webViewTitle = getString(R.string.no_title);
 
-            // Enter full screen video.
-            @Override
-            public void onShowCustomView(View video, CustomViewCallback callback) {
-                // Set the full screen video flag.
-                displayingFullScreenVideo = true;
+        // Initialize the favorite icon bitmap.  `ContextCompat` must be used until API >= 21.
+        Drawable favoriteIconDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.world);
+        BitmapDrawable favoriteIconBitmapDrawable = (BitmapDrawable) favoriteIconDrawable;
+        assert favoriteIconBitmapDrawable != null;
+        favoriteIconDefaultBitmap = favoriteIconBitmapDrawable.getBitmap();
 
-                // Pause the ad if this is the free flavor.
-                if (BuildConfig.FLAVOR.contentEquals("free")) {
-                    // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
-                    AdHelper.pauseAd(findViewById(R.id.adview));
-                }
+        // If the favorite icon is null, load the default.
+        if (favoriteIconBitmap == null) {
+            favoriteIconBitmap = favoriteIconDefaultBitmap;
+        }
 
-                // Hide the keyboard.
-                inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
+        // Initialize the user agent array adapter and string array.
+        userAgentNamesArray = ArrayAdapter.createFromResource(this, R.array.user_agent_names, R.layout.spinner_item);
+        userAgentDataArray = resources.getStringArray(R.array.user_agent_data);
 
-                // Hide the main content relative layout.
-                mainContentRelativeLayout.setVisibility(View.GONE);
+        // Get the intent that started the app.
+        Intent launchingIntent = getIntent();
 
-                // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
-                drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
+        // Get the information from the intent.
+        String launchingIntentAction = launchingIntent.getAction();
+        Uri launchingIntentUriData = launchingIntent.getData();
 
-                // Remove the translucent status flag.  This is necessary so the root frame layout can fill the entire screen.
-                getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+        // If the intent action is a web search, perform the search.
+        if ((launchingIntentAction != null) && launchingIntentAction.equals(Intent.ACTION_WEB_SEARCH)) {
+            // Create an encoded URL string.
+            String encodedUrlString;
 
-                /* Hide the system bars.
-                 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
-                 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
-                 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
-                 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
-                 */
-                rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
-                        View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
-
-                // Disable the sliding drawers.
-                drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
-
-                // Add the video view to the full screen video frame layout.
-                fullScreenVideoFrameLayout.addView(video);
-
-                // Show the full screen video frame layout.
-                fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
+            // Sanitize the search input and convert it to a search.
+            try {
+                encodedUrlString = URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8");
+            } catch (UnsupportedEncodingException exception) {
+                encodedUrlString = "";
             }
 
-            // Exit full screen video.
-            @Override
-            public void onHideCustomView() {
-                // Unset the full screen video flag.
-                displayingFullScreenVideo = false;
-
-                // Remove all the views from the full screen video frame layout.
-                fullScreenVideoFrameLayout.removeAllViews();
-
-                // Hide the full screen video frame layout.
-                fullScreenVideoFrameLayout.setVisibility(View.GONE);
+            // Add the base search URL.
+            formattedUrlString = searchURL + encodedUrlString;
+        } else if (launchingIntentUriData != null){  // Check to see if the intent contains a new URL.
+            // Set the formatted URL string.
+            formattedUrlString = launchingIntentUriData.toString();
+        }
+    }
 
-                // Enable the sliding drawers.
-                drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
+    @Override
+    protected void onNewIntent(Intent intent) {
+        // Sets the new intent as the activity intent, so that any future `getIntent()`s pick up this one instead of creating a new activity.
+        setIntent(intent);
 
-                // Show the main content relative layout.
-                mainContentRelativeLayout.setVisibility(View.VISIBLE);
+        // Get the information from the intent.
+        String intentAction = intent.getAction();
+        Uri intentUriData = intent.getData();
 
-                // Apply the appropriate full screen mode the `SYSTEM_UI` flags.
-                if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) {  // Privacy Browser is currently in full screen browsing mode.
-                    // Hide the app bar if specified.
-                    if (hideAppBar) {
-                        actionBar.hide();
-                    }
+        // If the intent action is a web search, perform the search.
+        if ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH)) {
+            // Create an encoded URL string.
+            String encodedUrlString;
 
-                    // Hide the banner ad in the free flavor.
-                    if (BuildConfig.FLAVOR.contentEquals("free")) {
-                        AdHelper.hideAd(findViewById(R.id.adview));
-                    }
+            // Sanitize the search input and convert it to a search.
+            try {
+                encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8");
+            } catch (UnsupportedEncodingException exception) {
+                encodedUrlString = "";
+            }
 
-                    // Remove the translucent status flag.  This is necessary so the root frame layout can fill the entire screen.
-                    getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+            // Add the base search URL.
+            formattedUrlString = searchURL + encodedUrlString;
+        } else if (intentUriData != null){  // Check to see if the intent contains a new URL.
+            // Set the formatted URL string.
+            formattedUrlString = intentUriData.toString();
+        }
 
-                    /* Hide the system bars.
-                     * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
-                     * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
-                     * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
-                     * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
-                     */
-                    rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
-                            View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
-                } else {  // Switch to normal viewing mode.
-                    // Remove the `SYSTEM_UI` flags from the root frame layout.
-                    rootFrameLayout.setSystemUiVisibility(0);
+        // Load the URL.
+        loadUrl(formattedUrlString);
 
-                    // Add the translucent status flag.
-                    getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
-                }
+        // Get a handle for the drawer layout.
+        DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
 
-                // Reload the ad for the free flavor if not in full screen mode.
-                if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
-                    // Reload the ad.
-                    AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
-                }
-            }
+        // Close the navigation drawer if it is open.
+        if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
+            drawerLayout.closeDrawer(GravityCompat.START);
+        }
 
-            // Upload files.
-            @Override
-            public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
-                // Show the file chooser if the device is running API >= 21.
-                if (Build.VERSION.SDK_INT >= 21) {
-                    // Store the file path callback.
-                    fileChooserCallback = filePathCallback;
+        // Close the bookmarks drawer if it is open.
+        if (drawerLayout.isDrawerVisible(GravityCompat.END)) {
+            drawerLayout.closeDrawer(GravityCompat.END);
+        }
 
-                    // Create an intent to open a chooser based ont the file chooser parameters.
-                    Intent fileChooserIntent = fileChooserParams.createIntent();
+        // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it.
+        currentWebView.requestFocus();
+    }
 
-                    // Open the file chooser.  Currently only one `startActivityForResult` exists in this activity, so the request code, used to differentiate them, is simply `0`.
-                    startActivityForResult(fileChooserIntent, 0);
-                }
-                return true;
-            }
-        });
+    @Override
+    public void onRestart() {
+        // Run the default commands.
+        super.onRestart();
 
-        // Register `mainWebView` for a context menu.  This is used to see link targets and download images.
-        registerForContextMenu(mainWebView);
+        // Make sure Orbot is running if Privacy Browser is proxying through Orbot.
+        if (proxyThroughOrbot) {
+            // Request Orbot to start.  If Orbot is already running no hard will be caused by this request.
+            Intent orbotIntent = new Intent("org.torproject.android.intent.action.START");
 
-        // Allow the downloading of files.
-        mainWebView.setDownloadListener((String url, String userAgent, String contentDisposition, String mimetype, long contentLength) -> {
-            // Check if the download should be processed by an external app.
-            if (downloadWithExternalApp) {  // Download with an external app.
-                openUrlWithExternalApp(url);
-            } else {  // Download with Android's download manager.
-                // Check to see if the WRITE_EXTERNAL_STORAGE permission has already been granted.
-                if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {  // The storage permission has not been granted.
-                    // The WRITE_EXTERNAL_STORAGE permission needs to be requested.
+            // Send the intent to the Orbot package.
+            orbotIntent.setPackage("org.torproject.android");
 
-                    // Store the variables for future use by `onRequestPermissionsResult()`.
-                    downloadUrl = url;
-                    downloadContentDisposition = contentDisposition;
-                    downloadContentLength = contentLength;
+            // Make it so.
+            sendBroadcast(orbotIntent);
+        }
 
-                    // Show a dialog if the user has previously denied the permission.
-                    if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
-                        // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
-                        DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
+        // Apply the app settings if returning from the Settings activity..
+        if (reapplyAppSettingsOnRestart) {
+            // Apply the app settings.
+            applyAppSettings();
 
-                        // Show the download location permission alert dialog.  The permission will be requested when the the dialog is closed.
-                        downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
-                    } else {  // Show the permission request directly.
-                        // Request the permission.  The download dialog will be launched by `onRequestPermissionResult()`.
-                        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
-                    }
-                } else {  // The storage permission has already been granted.
-                    // Get a handle for the download file alert dialog.
-                    DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength);
+            // Reload the webpage if displaying of images has been disabled in the Settings activity.
+            if (reloadOnRestart) {
+                // Reload the WebViews.
+                // TODO
+                currentWebView.reload();
 
-                    // Show the download file alert dialog.
-                    downloadFileDialogFragment.show(fragmentManager, getString(R.string.download));
-                }
+                // Reset `reloadOnRestartBoolean`.
+                reloadOnRestart = false;
             }
-        });
-
-        // Allow pinch to zoom.
-        mainWebView.getSettings().setBuiltInZoomControls(true);
 
-        // Hide zoom controls.
-        mainWebView.getSettings().setDisplayZoomControls(false);
-
-        // Don't allow mixed content (HTTP and HTTPS) on the same website.
-        if (Build.VERSION.SDK_INT >= 21) {
-            mainWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
+            // Reset the return from settings flag.
+            reapplyAppSettingsOnRestart = false;
         }
 
-        // Set the WebView to use a wide viewport.  Otherwise, some web pages will be scrunched and some content will render outside the screen.
-        mainWebView.getSettings().setUseWideViewPort(true);
+        // Apply the domain settings if returning from the Domains activity.
+        if (reapplyDomainSettingsOnRestart) {
+            // Reapply the domain settings.
+            applyDomainSettings(formattedUrlString, false, true);
 
-        // Set the WebView to load in overview mode (zoomed out to the maximum width).
-        mainWebView.getSettings().setLoadWithOverviewMode(true);
+            // Reset `reapplyDomainSettingsOnRestart`.
+            reapplyDomainSettingsOnRestart = false;
+        }
 
-        // Explicitly disable geolocation.
-        mainWebView.getSettings().setGeolocationEnabled(false);
+        // Load the URL on restart to apply changes to night mode.
+        if (loadUrlOnRestart) {
+            // Load the current `formattedUrlString`.
+            loadUrl(formattedUrlString);
 
-        // Initialize cookieManager.
-        cookieManager = CookieManager.getInstance();
+            // Reset `loadUrlOnRestart.
+            loadUrlOnRestart = false;
+        }
 
-        // Replace the header that `WebView` creates for `X-Requested-With` with a null value.  The default value is the application ID (com.stoutner.privacybrowser.standard).
-        customHeaders.put("X-Requested-With", "");
+        // Update the bookmarks drawer if returning from the Bookmarks activity.
+        if (restartFromBookmarksActivity) {
+            // Get a handle for the drawer layout.
+            DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
 
-        // Initialize the default preference values the first time the program is run.  `false` keeps this command from resetting any current preferences back to default.
-        PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
+            // Close the bookmarks drawer.
+            drawerLayout.closeDrawer(GravityCompat.END);
 
-        // Get a handle for the `Runtime`.
-        privacyBrowserRuntime = Runtime.getRuntime();
+            // Reload the bookmarks drawer.
+            loadBookmarksFolder();
 
-        // Store the application's private data directory.
-        privateDataDirectoryString = getApplicationInfo().dataDir;
-        // `dataDir` will vary, but will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, which links to `/data/data/com.stoutner.privacybrowser.standard`.
+            // Reset `restartFromBookmarksActivity`.
+            restartFromBookmarksActivity = false;
+        }
 
-        // Initialize `inFullScreenBrowsingMode`, which is always false at this point because Privacy Browser never starts in full screen browsing mode.
-        inFullScreenBrowsingMode = false;
+        // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.  This can be important if the screen was rotated.
+        updatePrivacyIcons(true);
+    }
 
-        // Initialize the privacy settings variables.
-        javaScriptEnabled = false;
-        firstPartyCookiesEnabled = false;
-        thirdPartyCookiesEnabled = false;
-        domStorageEnabled = false;
-        saveFormDataEnabled = false;  // Form data can be removed once the minimum API >= 26.
-        nightMode = false;
+    // `onResume()` runs after `onStart()`, which runs after `onCreate()` and `onRestart()`.
+    @Override
+    public void onResume() {
+        // Run the default commands.
+        super.onResume();
 
-        // Store the default user agent.
-        webViewDefaultUserAgent = mainWebView.getSettings().getUserAgentString();
+        // Resume JavaScript (if enabled).
+        // TODO mainWebView.resumeTimers();
 
-        // Initialize the WebView title.
-        webViewTitle = getString(R.string.no_title);
+        // Resume `mainWebView`.
+        // TODO mainWebView.onResume();
 
-        // Initialize the favorite icon bitmap.  `ContextCompat` must be used until API >= 21.
-        Drawable favoriteIconDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.world);
-        BitmapDrawable favoriteIconBitmapDrawable = (BitmapDrawable) favoriteIconDrawable;
-        assert favoriteIconBitmapDrawable != null;
-        favoriteIconDefaultBitmap = favoriteIconBitmapDrawable.getBitmap();
+        // Display a message to the user if waiting for Orbot.
+        if (waitingForOrbot && !orbotStatus.equals("ON")) {
+            // Disable the wide view port so that the waiting for Orbot text is displayed correctly.
+            currentWebView.getSettings().setUseWideViewPort(false);
 
-        // If the favorite icon is null, load the default.
-        if (favoriteIconBitmap == null) {
-            favoriteIconBitmap = favoriteIconDefaultBitmap;
+            // Load a waiting page.  `null` specifies no encoding, which defaults to ASCII.
+            currentWebView.loadData(waitingForOrbotHtmlString, "text/html", null);
         }
 
-        // Initialize the user agent array adapter and string array.
-        userAgentNamesArray = ArrayAdapter.createFromResource(this, R.array.user_agent_names, R.layout.spinner_item);
-        userAgentDataArray = resources.getStringArray(R.array.user_agent_data);
-
-        // Apply the app settings from the shared preferences.
-        applyAppSettings();
-
-        // Instantiate the block list helper.
-        BlockListHelper blockListHelper = new BlockListHelper();
+        if (displayingFullScreenVideo || inFullScreenBrowsingMode) {
+            // Get a handle for the root frame layouts.
+            FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
 
-        // Initialize the list of resource requests.
-        resourceRequests = new ArrayList<>();
+            // Remove the translucent status flag.  This is necessary so the root frame layout can fill the entire screen.
+            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
 
-        // Parse the block lists.
-        final ArrayList<List<String[]>> easyList = blockListHelper.parseBlockList(getAssets(), "blocklists/easylist.txt");
-        final ArrayList<List<String[]>> easyPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/easyprivacy.txt");
-        final ArrayList<List<String[]>> fanboysAnnoyanceList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-annoyance.txt");
-        final ArrayList<List<String[]>> fanboysSocialList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-social.txt");
-        final ArrayList<List<String[]>> ultraPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/ultraprivacy.txt");
+            /* Hide the system bars.
+             * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+             * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+             * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+             * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+             */
+            rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+                    View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+        } else if (BuildConfig.FLAVOR.contentEquals("free")) {  // Resume the adView for the free flavor.
+            // Resume the ad.
+            AdHelper.resumeAd(findViewById(R.id.adview));
+        }
+    }
 
-        // Store the list versions.
-        easyListVersion = easyList.get(0).get(0)[0];
-        easyPrivacyVersion = easyPrivacy.get(0).get(0)[0];
-        fanboysAnnoyanceVersion = fanboysAnnoyanceList.get(0).get(0)[0];
-        fanboysSocialVersion = fanboysSocialList.get(0).get(0)[0];
-        ultraPrivacyVersion = ultraPrivacy.get(0).get(0)[0];
+    @Override
+    public void onPause() {
+        // Run the default commands.
+        super.onPause();
 
-        // Get a handle for the activity.  This is used to update the requests counter while the navigation menu is open.
-        Activity activity = this;
+        // Pause `mainWebView`.
+        // TODO
+        currentWebView.onPause();
 
-        mainWebView.setWebViewClient(new WebViewClient() {
-            // `shouldOverrideUrlLoading` makes this `WebView` the default handler for URLs inside the app, so that links are not kicked out to other apps.
-            // The deprecated `shouldOverrideUrlLoading` must be used until API >= 24.
-            @SuppressWarnings("deprecation")
-            @Override
-            public boolean shouldOverrideUrlLoading(WebView view, String url) {
-                if (url.startsWith("http")) {  // Load the URL in Privacy Browser.
-                    // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
-                    formattedUrlString = "";
+        // Stop all JavaScript.
+        // TODO
+        currentWebView.pauseTimers();
 
-                    // Apply the domain settings for the new URL.  `applyDomainSettings` doesn't do anything if the domain has not changed.
-                    boolean userAgentChanged = applyDomainSettings(url, true, false);
+        // Pause the ad or it will continue to consume resources in the background on the free flavor.
+        if (BuildConfig.FLAVOR.contentEquals("free")) {
+            // Pause the ad.
+            AdHelper.pauseAd(findViewById(R.id.adview));
+        }
+    }
 
-                    // Check if the user agent has changed.
-                    if (userAgentChanged) {
-                        // Manually load the URL.  The changing of the user agent will cause WebView to reload the previous URL.
-                        mainWebView.loadUrl(url, customHeaders);
+    @Override
+    public void onDestroy() {
+        // Unregister the Orbot status broadcast receiver.
+        this.unregisterReceiver(orbotStatusBroadcastReceiver);
 
-                        // Returning true indicates that Privacy Browser is manually handling the loading of the URL.
-                        return true;
-                    } else {
-                        // Returning false causes the current WebView to handle the URL and prevents it from adding redirects to the history list.
-                        return false;
-                    }
-                } else if (url.startsWith("mailto:")) {  // Load the email address in an external email program.
-                    // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
-                    Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
+        // Close the bookmarks cursor and database.
+        bookmarksCursor.close();
+        bookmarksDatabaseHelper.close();
 
-                    // Parse the url and set it as the data for the intent.
-                    emailIntent.setData(Uri.parse(url));
+        // Run the default commands.
+        super.onDestroy();
+    }
 
-                    // Open the email program in a new task instead of as part of Privacy Browser.
-                    emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        // Inflate the menu.  This adds items to the action bar if it is present.
+        getMenuInflater().inflate(R.menu.webview_options_menu, menu);
 
-                    // Make it so.
-                    startActivity(emailIntent);
+        // Set mainMenu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons`.
+        mainMenu = menu;
 
-                    // Returning true indicates Privacy Browser is handling the URL by creating an intent.
-                    return true;
-                } else if (url.startsWith("tel:")) {  // Load the phone number in the dialer.
-                    // Open the dialer and load the phone number, but wait for the user to place the call.
-                    Intent dialIntent = new Intent(Intent.ACTION_DIAL);
+        // Set the initial status of the privacy icons.  `false` does not call `invalidateOptionsMenu` as the last step.
+        updatePrivacyIcons(false);
 
-                    // Add the phone number to the intent.
-                    dialIntent.setData(Uri.parse(url));
+        // Get handles for the menu items.
+        MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
+        MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
+        MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
+        MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data);  // Form data can be removed once the minimum API >= 26.
+        MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data);  // Form data can be removed once the minimum API >= 26.
+        refreshMenuItem = menu.findItem(R.id.refresh);
+        blocklistsMenuItem = menu.findItem(R.id.blocklists);
+        easyListMenuItem = menu.findItem(R.id.easylist);
+        easyPrivacyMenuItem = menu.findItem(R.id.easyprivacy);
+        fanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list);
+        fanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list);
+        ultraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy);
+        blockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
+        MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent);
 
-                    // Open the dialer in a new task instead of as part of Privacy Browser.
-                    dialIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        // Only display third-party cookies if API >= 21
+        toggleThirdPartyCookiesMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
 
-                    // Make it so.
-                    startActivity(dialIntent);
+        // Only display the form data menu items if the API < 26.
+        toggleSaveFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
+        clearFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
 
-                    // Returning true indicates Privacy Browser is handling the URL by creating an intent.
-                    return true;
-                } else {  // Load a system chooser to select an app that can handle the URL.
-                    // Open an app that can handle the URL.
-                    Intent genericIntent = new Intent(Intent.ACTION_VIEW);
+        // Only show Ad Consent if this is the free flavor.
+        adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free"));
 
-                    // Add the URL to the intent.
-                    genericIntent.setData(Uri.parse(url));
+        // Get the shared preference values.
+        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
-                    // List all apps that can handle the URL instead of just opening the first one.
-                    genericIntent.addCategory(Intent.CATEGORY_BROWSABLE);
+        // Get the status of the additional AppBar icons.
+        displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false);
 
-                    // Open the app in a new task instead of as part of Privacy Browser.
-                    genericIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        // Set the status of the additional app bar icons.  Setting the refresh menu item to `SHOW_AS_ACTION_ALWAYS` makes it appear even on small devices like phones.
+        if (displayAdditionalAppBarIcons) {
+            toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+            toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+            refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
+        } else { //Do not display the additional icons.
+            toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+            toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+            refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+        }
 
-                    // Start the app or display a snackbar if no app is available to handle the URL.
-                    try {
-                        startActivity(genericIntent);
-                    } catch (ActivityNotFoundException exception) {
-                        Snackbar.make(mainWebView, getString(R.string.unrecognized_url) + "  " + url, Snackbar.LENGTH_SHORT).show();
-                    }
+        // Replace Refresh with Stop if a URL is already loading.
+        if (urlIsLoading) {
+            // Set the title.
+            refreshMenuItem.setTitle(R.string.stop);
 
-                    // Returning true indicates Privacy Browser is handling the URL by creating an intent.
-                    return true;
+            // If the icon is displayed in the AppBar, set it according to the theme.
+            if (displayAdditionalAppBarIcons) {
+                if (darkTheme) {
+                    refreshMenuItem.setIcon(R.drawable.close_dark);
+                } else {
+                    refreshMenuItem.setIcon(R.drawable.close_light);
                 }
             }
+        }
 
-            // Check requests against the block lists.  The deprecated `shouldInterceptRequest()` must be used until minimum API >= 21.
-            @SuppressWarnings("deprecation")
-            @Override
-            public WebResourceResponse shouldInterceptRequest(WebView view, String url){
-                // Create an empty web resource response to be used if the resource request is blocked.
-                WebResourceResponse emptyWebResourceResponse = new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes()));
+        return true;
+    }
 
-                // Reset the whitelist results tracker.
-                whiteListResultStringArray = null;
+    @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        // Get a handle for the swipe refresh layout.
+        SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
 
-                // Initialize the third party request tracker.
-                boolean isThirdPartyRequest = false;
+        // Get handles for the menu items.
+        MenuItem addOrEditDomain = menu.findItem(R.id.add_or_edit_domain);
+        MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
+        MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
+        MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
+        MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data);  // Form data can be removed once the minimum API >= 26.
+        MenuItem clearDataMenuItem = menu.findItem(R.id.clear_data);
+        MenuItem clearCookiesMenuItem = menu.findItem(R.id.clear_cookies);
+        MenuItem clearDOMStorageMenuItem = menu.findItem(R.id.clear_dom_storage);
+        MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data);  // Form data can be removed once the minimum API >= 26.
+        MenuItem fontSizeMenuItem = menu.findItem(R.id.font_size);
+        MenuItem swipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh);
+        MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images);
+        MenuItem nightModeMenuItem = menu.findItem(R.id.night_mode);
+        MenuItem proxyThroughOrbotMenuItem = menu.findItem(R.id.proxy_through_orbot);
 
-                // Initialize the current domain string.
-                String currentDomain = "";
+        // Set the text for the domain menu item.
+        if (domainSettingsApplied) {
+            addOrEditDomain.setTitle(R.string.edit_domain_settings);
+        } else {
+            addOrEditDomain.setTitle(R.string.add_domain_settings);
+        }
 
-                // Nobody is happy when comparing null strings.
-                if (!(formattedUrlString == null) && !(url == null)) {
-                    // Get the domain strings to URIs.
-                    Uri currentDomainUri = Uri.parse(formattedUrlString);
-                    Uri requestDomainUri = Uri.parse(url);
+        // Set the status of the menu item checkboxes.
+        toggleFirstPartyCookiesMenuItem.setChecked(firstPartyCookiesEnabled);
+        toggleThirdPartyCookiesMenuItem.setChecked(thirdPartyCookiesEnabled);
+        toggleDomStorageMenuItem.setChecked(domStorageEnabled);
+        toggleSaveFormDataMenuItem.setChecked(saveFormDataEnabled);  // Form data can be removed once the minimum API >= 26.
+        easyListMenuItem.setChecked(easyListEnabled);
+        easyPrivacyMenuItem.setChecked(easyPrivacyEnabled);
+        fanboysAnnoyanceListMenuItem.setChecked(fanboysAnnoyanceListEnabled);
+        fanboysSocialBlockingListMenuItem.setChecked(fanboysSocialBlockingListEnabled);
+        ultraPrivacyMenuItem.setChecked(ultraPrivacyEnabled);
+        blockAllThirdPartyRequestsMenuItem.setChecked(blockAllThirdPartyRequests);
+        swipeToRefreshMenuItem.setChecked(swipeRefreshLayout.isEnabled());
+        // TODO displayImagesMenuItem.setChecked(mainWebView.getSettings().getLoadsImagesAutomatically());
+        nightModeMenuItem.setChecked(nightMode);
+        proxyThroughOrbotMenuItem.setChecked(proxyThroughOrbot);
 
-                    // Get the domain host names.
-                    String currentBaseDomain = currentDomainUri.getHost();
-                    String requestBaseDomain = requestDomainUri.getHost();
+        // Enable third-party cookies if first-party cookies are enabled.
+        toggleThirdPartyCookiesMenuItem.setEnabled(firstPartyCookiesEnabled);
 
-                    // Update the current domain variable.
-                    currentDomain = currentBaseDomain;
+        // Enable DOM Storage if JavaScript is enabled.
+        toggleDomStorageMenuItem.setEnabled(javaScriptEnabled);
 
-                    // Only compare the current base domain and the request base domain if neither is null.
-                    if (!(currentBaseDomain == null) && !(requestBaseDomain == null)) {
-                        // Determine the current base domain.
-                        while (currentBaseDomain.indexOf(".", currentBaseDomain.indexOf(".") + 1) > 0) {  // There is at least one subdomain.
-                            // Remove the first subdomain.
-                            currentBaseDomain = currentBaseDomain.substring(currentBaseDomain.indexOf(".") + 1);
-                        }
+        // Enable Clear Cookies if there are any.
+        clearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
 
-                        // Determine the request base domain.
-                        while (requestBaseDomain.indexOf(".", requestBaseDomain.indexOf(".") + 1) > 0) {  // There is at least one subdomain.
-                            // Remove the first subdomain.
-                            requestBaseDomain = requestBaseDomain.substring(requestBaseDomain.indexOf(".") + 1);
-                        }
+        // Get a count of the number of files in the Local Storage directory.
+        File localStorageDirectory = new File (privateDataDirectoryString + "/app_webview/Local Storage/");
+        int localStorageDirectoryNumberOfFiles = 0;
+        if (localStorageDirectory.exists()) {
+            localStorageDirectoryNumberOfFiles = localStorageDirectory.list().length;
+        }
 
-                        // Update the third party request tracker.
-                        isThirdPartyRequest = !currentBaseDomain.equals(requestBaseDomain);
-                    }
-                }
+        // Get a count of the number of files in the IndexedDB directory.
+        File indexedDBDirectory = new File (privateDataDirectoryString + "/app_webview/IndexedDB");
+        int indexedDBDirectoryNumberOfFiles = 0;
+        if (indexedDBDirectory.exists()) {
+            indexedDBDirectoryNumberOfFiles = indexedDBDirectory.list().length;
+        }
 
-                // Block third-party requests if enabled.
-                if (isThirdPartyRequest && blockAllThirdPartyRequests) {
-                    // Increment the blocked requests counters.
-                    blockedRequests++;
-                    thirdPartyBlockedRequests++;
+        // Enable Clear DOM Storage if there is any.
+        clearDOMStorageMenuItem.setEnabled(localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0);
 
-                    // Update the titles of the blocklist menu items.  This must be run from the UI thread.
-                    activity.runOnUiThread(() -> {
-                        navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
-                        blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
-                        blockAllThirdPartyRequestsMenuItem.setTitle(thirdPartyBlockedRequests + " - " + getString(R.string.block_all_third_party_requests));
-                    });
+        // Enable Clear Form Data is there is any.  This can be removed once the minimum API >= 26.
+        if (Build.VERSION.SDK_INT < 26) {
+            WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
+            clearFormDataMenuItem.setEnabled(mainWebViewDatabase.hasFormData());
+        } else {
+            // Disable clear form data because it is not supported on current version of Android.
+            clearFormDataMenuItem.setEnabled(false);
+        }
 
-                    // Add the request to the log.
-                    resourceRequests.add(new String[]{String.valueOf(REQUEST_THIRD_PARTY), url});
+        // Enable Clear Data if any of the submenu items are enabled.
+        clearDataMenuItem.setEnabled(clearCookiesMenuItem.isEnabled() || clearDOMStorageMenuItem.isEnabled() || clearFormDataMenuItem.isEnabled());
 
-                    // Return an empty web resource response.
-                    return emptyWebResourceResponse;
-                }
+        // Disable Fanboy's Social Blocking List if Fanboy's Annoyance List is checked.
+        fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListEnabled);
 
-                // Check UltraPrivacy if it is enabled.
-                if (ultraPrivacyEnabled) {
-                    if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, ultraPrivacy)) {
-                        // Increment the blocked requests counters.
-                        blockedRequests++;
-                        ultraPrivacyBlockedRequests++;
+        // Initialize the display names for the blocklists with the number of blocked requests.
+        blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + blockedRequests);
+        easyListMenuItem.setTitle(easyListBlockedRequests + " - " + getString(R.string.easylist));
+        easyPrivacyMenuItem.setTitle(easyPrivacyBlockedRequests + " - " + getString(R.string.easyprivacy));
+        fanboysAnnoyanceListMenuItem.setTitle(fanboysAnnoyanceListBlockedRequests + " - " + getString(R.string.fanboys_annoyance_list));
+        fanboysSocialBlockingListMenuItem.setTitle(fanboysSocialBlockingListBlockedRequests + " - " + getString(R.string.fanboys_social_blocking_list));
+        ultraPrivacyMenuItem.setTitle(ultraPrivacyBlockedRequests + " - " + getString(R.string.ultraprivacy));
+        blockAllThirdPartyRequestsMenuItem.setTitle(thirdPartyBlockedRequests + " - " + getString(R.string.block_all_third_party_requests));
 
-                        // Update the titles of the blocklist menu items.  This must be run from the UI thread.
-                        activity.runOnUiThread(() -> {
-                            navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
-                            blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
-                            ultraPrivacyMenuItem.setTitle(ultraPrivacyBlockedRequests + " - " + getString(R.string.ultraprivacy));
-                        });
+        // Get the current user agent.
+        // TODO String currentUserAgent = mainWebView.getSettings().getUserAgentString();
+        String currentUserAgent = "";
 
-                        // The resource request was blocked.  Return an empty web resource response.
-                        return emptyWebResourceResponse;
-                    }
+        // Select the current user agent menu item.  A switch statement cannot be used because the user agents are not compile time constants.
+        if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[0])) {  // Privacy Browser.
+            menu.findItem(R.id.user_agent_privacy_browser).setChecked(true);
+        } else if (currentUserAgent.equals(webViewDefaultUserAgent)) {  // WebView Default.
+            menu.findItem(R.id.user_agent_webview_default).setChecked(true);
+        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[2])) {  // Firefox on Android.
+            menu.findItem(R.id.user_agent_firefox_on_android).setChecked(true);
+        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[3])) {  // Chrome on Android.
+            menu.findItem(R.id.user_agent_chrome_on_android).setChecked(true);
+        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[4])) {  // Safari on iOS.
+            menu.findItem(R.id.user_agent_safari_on_ios).setChecked(true);
+        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[5])) {  // Firefox on Linux.
+            menu.findItem(R.id.user_agent_firefox_on_linux).setChecked(true);
+        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[6])) {  // Chromium on Linux.
+            menu.findItem(R.id.user_agent_chromium_on_linux).setChecked(true);
+        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[7])) {  // Firefox on Windows.
+            menu.findItem(R.id.user_agent_firefox_on_windows).setChecked(true);
+        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[8])) {  // Chrome on Windows.
+            menu.findItem(R.id.user_agent_chrome_on_windows).setChecked(true);
+        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[9])) {  // Edge on Windows.
+            menu.findItem(R.id.user_agent_edge_on_windows).setChecked(true);
+        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[10])) {  // Internet Explorer on Windows.
+            menu.findItem(R.id.user_agent_internet_explorer_on_windows).setChecked(true);
+        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[11])) {  // Safari on macOS.
+            menu.findItem(R.id.user_agent_safari_on_macos).setChecked(true);
+        } else {  // Custom user agent.
+            menu.findItem(R.id.user_agent_custom).setChecked(true);
+        }
 
-                    // If the whitelist result is not null, the request has been allowed by UltraPrivacy.
-                    if (whiteListResultStringArray != null) {
-                        // Add a whitelist entry to the resource requests array.
-                        resourceRequests.add(whiteListResultStringArray);
+        // Initialize font size variables.
+        // TODO int fontSize = mainWebView.getSettings().getTextZoom();
+        int fontSize = 100;
+        String fontSizeTitle;
+        MenuItem selectedFontSizeMenuItem;
 
-                        // The resource request has been allowed by UltraPrivacy.  `return null` loads the requested resource.
-                        return null;
-                    }
+        // Prepare the font size title and current size menu item.
+        switch (fontSize) {
+            case 25:
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.twenty_five_percent);
+                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_twenty_five_percent);
+                break;
+
+            case 50:
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.fifty_percent);
+                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_fifty_percent);
+                break;
+
+            case 75:
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.seventy_five_percent);
+                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_seventy_five_percent);
+                break;
+
+            case 100:
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
+                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
+                break;
+
+            case 125:
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_twenty_five_percent);
+                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_twenty_five_percent);
+                break;
+
+            case 150:
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_fifty_percent);
+                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_fifty_percent);
+                break;
+
+            case 175:
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_seventy_five_percent);
+                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_seventy_five_percent);
+                break;
+
+            case 200:
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.two_hundred_percent);
+                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_two_hundred_percent);
+                break;
+
+            default:
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
+                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
+                break;
+        }
+
+        // Set the font size title and select the current size menu item.
+        fontSizeMenuItem.setTitle(fontSizeTitle);
+        selectedFontSizeMenuItem.setChecked(true);
+
+        // Run all the other default commands.
+        super.onPrepareOptionsMenu(menu);
+
+        // Display the menu.
+        return true;
+    }
+
+    @Override
+    // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
+    @SuppressLint("SetJavaScriptEnabled")
+    // removeAllCookies is deprecated, but it is required for API < 21.
+    @SuppressWarnings("deprecation")
+    public boolean onOptionsItemSelected(MenuItem menuItem) {
+        // Reenter full screen browsing mode if it was interrupted by the options menu.  <https://redmine.stoutner.com/issues/389>
+        if (inFullScreenBrowsingMode) {
+            // Remove the translucent status flag.  This is necessary so the root frame layout can fill the entire screen.
+            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+
+            FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
+
+            /* Hide the system bars.
+             * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+             * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+             * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+             * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+             */
+            rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+                    View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+        }
+
+        // Get the selected menu item ID.
+        int menuItemId = menuItem.getItemId();
+
+        // Run the commands that correlate to the selected menu item.
+        switch (menuItemId) {
+            case R.id.toggle_javascript:
+                // Switch the status of javaScriptEnabled.
+                javaScriptEnabled = !javaScriptEnabled;
+
+                // Apply the new JavaScript status.
+                currentWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
+
+                // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
+                updatePrivacyIcons(true);
+
+                // Display a `Snackbar`.
+                if (javaScriptEnabled) {  // JavaScrip is enabled.
+                    Snackbar.make(findViewById(R.id.webviewpager), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
+                } else if (firstPartyCookiesEnabled) {  // JavaScript is disabled, but first-party cookies are enabled.
+                    Snackbar.make(findViewById(R.id.webviewpager), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
+                } else {  // Privacy mode.
+                    Snackbar.make(findViewById(R.id.webviewpager), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
                 }
 
-                // Check EasyList if it is enabled.
-                if (easyListEnabled) {
-                    if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, easyList)) {
-                        // Increment the blocked requests counters.
-                        blockedRequests++;
-                        easyListBlockedRequests++;
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-                        // Update the titles of the blocklist menu items.  This must be run from the UI thread.
-                        activity.runOnUiThread(() -> {
-                            navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
-                            blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
-                            easyListMenuItem.setTitle(easyListBlockedRequests + " - " + getString(R.string.easylist));
-                        });
+            case R.id.add_or_edit_domain:
+                if (domainSettingsApplied) {  // Edit the current domain settings.
+                    // Reapply the domain settings on returning to `MainWebViewActivity`.
+                    reapplyDomainSettingsOnRestart = true;
+                    currentDomainName = "";
 
-                        // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
-                        whiteListResultStringArray = null;
+                    // Create an intent to launch the domains activity.
+                    Intent domainsIntent = new Intent(this, DomainsActivity.class);
 
-                        // The resource request was blocked.  Return an empty web resource response.
-                        return emptyWebResourceResponse;
-                    }
+                    // Put extra information instructing the domains activity to directly load the current domain and close on back instead of returning to the domains list.
+                    domainsIntent.putExtra("loadDomain", domainSettingsDatabaseId);
+                    domainsIntent.putExtra("closeOnBack", true);
+
+                    // Make it so.
+                    startActivity(domainsIntent);
+                } else {  // Add a new domain.
+                    // Apply the new domain settings on returning to `MainWebViewActivity`.
+                    reapplyDomainSettingsOnRestart = true;
+                    currentDomainName = "";
+
+                    // Get the current domain
+                    Uri currentUri = Uri.parse(formattedUrlString);
+                    String currentDomain = currentUri.getHost();
+
+                    // Initialize the database handler.  The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
+                    DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
+
+                    // Create the domain and store the database ID.
+                    int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain);
+
+                    // Create an intent to launch the domains activity.
+                    Intent domainsIntent = new Intent(this, DomainsActivity.class);
+
+                    // Put extra information instructing the domains activity to directly load the new domain and close on back instead of returning to the domains list.
+                    domainsIntent.putExtra("loadDomain", newDomainDatabaseId);
+                    domainsIntent.putExtra("closeOnBack", true);
+
+                    // Make it so.
+                    startActivity(domainsIntent);
                 }
+                return true;
 
-                // Check EasyPrivacy if it is enabled.
-                if (easyPrivacyEnabled) {
-                    if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, easyPrivacy)) {
-                        // Increment the blocked requests counters.
-                        blockedRequests++;
-                        easyPrivacyBlockedRequests++;
+            case R.id.toggle_first_party_cookies:
+                // Switch the status of firstPartyCookiesEnabled.
+                firstPartyCookiesEnabled = !firstPartyCookiesEnabled;
 
-                        // Update the titles of the blocklist menu items.  This must be run from the UI thread.
-                        activity.runOnUiThread(() -> {
-                            navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
-                            blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
-                            easyPrivacyMenuItem.setTitle(easyPrivacyBlockedRequests + " - " + getString(R.string.easyprivacy));
-                        });
+                // Update the menu checkbox.
+                menuItem.setChecked(firstPartyCookiesEnabled);
 
-                        // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
-                        whiteListResultStringArray = null;
+                // Apply the new cookie status.
+                cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
 
-                        // The resource request was blocked.  Return an empty web resource response.
-                        return emptyWebResourceResponse;
-                    }
+                // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
+                updatePrivacyIcons(true);
+
+                // Display a `Snackbar`.
+                if (firstPartyCookiesEnabled) {  // First-party cookies are enabled.
+                    Snackbar.make(findViewById(R.id.webviewpager), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
+                } else if (javaScriptEnabled) {  // JavaScript is still enabled.
+                    Snackbar.make(findViewById(R.id.webviewpager), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
+                } else {  // Privacy mode.
+                    Snackbar.make(findViewById(R.id.webviewpager), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
                 }
 
-                // Check Fanboy’s Annoyance List if it is enabled.
-                if (fanboysAnnoyanceListEnabled) {
-                    if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, fanboysAnnoyanceList)) {
-                        // Increment the blocked requests counters.
-                        blockedRequests++;
-                        fanboysAnnoyanceListBlockedRequests++;
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-                        // Update the titles of the blocklist menu items.  This must be run from the UI thread.
-                        activity.runOnUiThread(() -> {
-                            navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
-                            blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
-                            fanboysAnnoyanceListMenuItem.setTitle(fanboysAnnoyanceListBlockedRequests + " - " + getString(R.string.fanboys_annoyance_list));
-                        });
+            case R.id.toggle_third_party_cookies:
+                if (Build.VERSION.SDK_INT >= 21) {
+                    // Switch the status of thirdPartyCookiesEnabled.
+                    thirdPartyCookiesEnabled = !thirdPartyCookiesEnabled;
 
-                        // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
-                        whiteListResultStringArray = null;
+                    // Update the menu checkbox.
+                    menuItem.setChecked(thirdPartyCookiesEnabled);
 
-                        // The resource request was blocked.  Return an empty web resource response.
-                        return emptyWebResourceResponse;
+                    // Apply the new cookie status.
+                    cookieManager.setAcceptThirdPartyCookies(currentWebView, thirdPartyCookiesEnabled);
+
+                    // Display a `Snackbar`.
+                    if (thirdPartyCookiesEnabled) {
+                        Snackbar.make(findViewById(R.id.webviewpager), R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
+                    } else {
+                        Snackbar.make(findViewById(R.id.webviewpager), R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
                     }
-                } else if (fanboysSocialBlockingListEnabled){  // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled.
-                    if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, fanboysSocialList)) {
-                        // Increment the blocked requests counters.
-                        blockedRequests++;
-                        fanboysSocialBlockingListBlockedRequests++;
 
-                        // Update the titles of the blocklist menu items.  This must be run from the UI thread.
-                        activity.runOnUiThread(() -> {
-                            navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
-                            blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
-                            fanboysSocialBlockingListMenuItem.setTitle(fanboysSocialBlockingListBlockedRequests + " - " + getString(R.string.fanboys_social_blocking_list));
-                        });
+                    // Reload the current WebView.
+                    currentWebView.reload();
+                } // Else do nothing because SDK < 21.
+                return true;
 
-                        // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
-                        whiteListResultStringArray = null;
+            case R.id.toggle_dom_storage:
+                // Switch the status of domStorageEnabled.
+                domStorageEnabled = !domStorageEnabled;
 
-                        // The resource request was blocked.  Return an empty web resource response.
-                        return emptyWebResourceResponse;
-                    }
+                // Update the menu checkbox.
+                menuItem.setChecked(domStorageEnabled);
+
+                // Apply the new DOM Storage status.
+                currentWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
+
+                // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
+                updatePrivacyIcons(true);
+
+                // Display a `Snackbar`.
+                if (domStorageEnabled) {
+                    Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
+                } else {
+                    Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
                 }
 
-                // Add the request to the log because it hasn't been processed by any of the previous checks.
-                if (whiteListResultStringArray != null ) {  // The request was processed by a whitelist.
-                    resourceRequests.add(whiteListResultStringArray);
-                } else {  // The request didn't match any blocklist entry.  Log it as a default request.
-                    resourceRequests.add(new String[]{String.valueOf(REQUEST_DEFAULT), url});
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
+
+            // Form data can be removed once the minimum API >= 26.
+            case R.id.toggle_save_form_data:
+                // Switch the status of saveFormDataEnabled.
+                saveFormDataEnabled = !saveFormDataEnabled;
+
+                // Update the menu checkbox.
+                menuItem.setChecked(saveFormDataEnabled);
+
+                // Apply the new form data status.
+                currentWebView.getSettings().setSaveFormData(saveFormDataEnabled);
+
+                // Display a `Snackbar`.
+                if (saveFormDataEnabled) {
+                    Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
+                } else {
+                    Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
                 }
 
-                // The resource request has not been blocked.  `return null` loads the requested resource.
-                return null;
-            }
+                // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
+                updatePrivacyIcons(true);
+
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
+
+            case R.id.clear_cookies:
+                Snackbar.make(findViewById(R.id.webviewpager), R.string.cookies_deleted, Snackbar.LENGTH_LONG)
+                        .setAction(R.string.undo, v -> {
+                            // Do nothing because everything will be handled by `onDismissed()` below.
+                        })
+                        .addCallback(new Snackbar.Callback() {
+                            @SuppressLint("SwitchIntDef")  // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
+                            @Override
+                            public void onDismissed(Snackbar snackbar, int event) {
+                                switch (event) {
+                                    // The user pushed the undo button.
+                                    case Snackbar.Callback.DISMISS_EVENT_ACTION:
+                                        // Do nothing.
+                                        break;
+
+                                    // The snackbar was dismissed without the undo button being pushed.
+                                    default:
+                                        // `cookieManager.removeAllCookie()` varies by SDK.
+                                        if (Build.VERSION.SDK_INT < 21) {
+                                            cookieManager.removeAllCookie();
+                                        } else {
+                                            cookieManager.removeAllCookies(null);
+                                        }
+                                }
+                            }
+                        })
+                        .show();
+                return true;
+
+            case R.id.clear_dom_storage:
+                Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_deleted, Snackbar.LENGTH_LONG)
+                        .setAction(R.string.undo, v -> {
+                            // Do nothing because everything will be handled by `onDismissed()` below.
+                        })
+                        .addCallback(new Snackbar.Callback() {
+                            @SuppressLint("SwitchIntDef")  // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
+                            @Override
+                            public void onDismissed(Snackbar snackbar, int event) {
+                                switch (event) {
+                                    // The user pushed the undo button.
+                                    case Snackbar.Callback.DISMISS_EVENT_ACTION:
+                                        // Do nothing.
+                                        break;
+
+                                    // The snackbar was dismissed without the undo button being pushed.
+                                    default:
+                                        // Delete the DOM Storage.
+                                        WebStorage webStorage = WebStorage.getInstance();
+                                        webStorage.deleteAllData();
+
+                                        // Initialize a handler to manually delete the DOM storage files and directories.
+                                        Handler deleteDomStorageHandler = new Handler();
+
+                                        // Setup a runnable to manually delete the DOM storage files and directories.
+                                        Runnable deleteDomStorageRunnable = () -> {
+                                            try {
+                                                // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
+                                                Process deleteLocalStorageProcess = privacyBrowserRuntime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
+
+                                                // Multiple commands must be used because `Runtime.exec()` does not like `*`.
+                                                Process deleteIndexProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
+                                                Process deleteQuotaManagerProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
+                                                Process deleteQuotaManagerJournalProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
+                                                Process deleteDatabasesProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
 
-            // Handle HTTP authentication requests.
-            @Override
-            public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
-                // Store `handler` so it can be accessed from `onHttpAuthenticationCancel()` and `onHttpAuthenticationProceed()`.
-                httpAuthHandler = handler;
+                                                // Wait for the processes to finish.
+                                                deleteLocalStorageProcess.waitFor();
+                                                deleteIndexProcess.waitFor();
+                                                deleteQuotaManagerProcess.waitFor();
+                                                deleteQuotaManagerJournalProcess.waitFor();
+                                                deleteDatabasesProcess.waitFor();
+                                            } catch (Exception exception) {
+                                                // Do nothing if an error is thrown.
+                                            }
+                                        };
 
-                // Display the HTTP authentication dialog.
-                DialogFragment httpAuthenticationDialogFragment = HttpAuthenticationDialog.displayDialog(host, realm);
-                httpAuthenticationDialogFragment.show(fragmentManager, getString(R.string.http_authentication));
-            }
+                                        // Manually delete the DOM storage files after 200 milliseconds.
+                                        deleteDomStorageHandler.postDelayed(deleteDomStorageRunnable, 200);
+                                }
+                            }
+                        })
+                        .show();
+                return true;
 
-            // Update the URL in urlTextBox when the page starts to load.
-            @Override
-            public void onPageStarted(WebView view, String url, Bitmap favicon) {
-                // Set `urlIsLoading` to `true`, so that redirects while loading do not trigger changes in the user agent, which forces another reload of the existing page.
-                // This is also used to determine when to check for pinned mismatches.
-                urlIsLoading = true;
+            // Form data can be remove once the minimum API >= 26.
+            case R.id.clear_form_data:
+                Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_deleted, Snackbar.LENGTH_LONG)
+                        .setAction(R.string.undo, v -> {
+                            // Do nothing because everything will be handled by `onDismissed()` below.
+                        })
+                        .addCallback(new Snackbar.Callback() {
+                            @SuppressLint("SwitchIntDef")  // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
+                            @Override
+                            public void onDismissed(Snackbar snackbar, int event) {
+                                switch (event) {
+                                    // The user pushed the undo button.
+                                    case Snackbar.Callback.DISMISS_EVENT_ACTION:
+                                        // Do nothing.
+                                        break;
 
-                // Reset the list of host IP addresses.
-                currentHostIpAddresses = "";
+                                    // The snackbar was dismissed without the `Undo` button being pushed.
+                                    default:
+                                        // Delete the form data.
+                                        WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(getApplicationContext());
+                                        mainWebViewDatabase.clearFormData();
+                                }
+                            }
+                        })
+                        .show();
+                return true;
 
-                // Reset the list of resource requests.
-                resourceRequests.clear();
+            case R.id.easylist:
+                // Toggle the EasyList status.
+                easyListEnabled = !easyListEnabled;
 
-                // Initialize the counters for requests blocked by each blocklist.
-                blockedRequests = 0;
-                easyListBlockedRequests = 0;
-                easyPrivacyBlockedRequests = 0;
-                fanboysAnnoyanceListBlockedRequests = 0;
-                fanboysSocialBlockingListBlockedRequests = 0;
-                ultraPrivacyBlockedRequests = 0;
-                thirdPartyBlockedRequests = 0;
+                // Update the menu checkbox.
+                menuItem.setChecked(easyListEnabled);
 
-                // If night mode is enabled, hide `mainWebView` until after the night mode CSS is applied.
-                if (nightMode) {
-                    mainWebView.setVisibility(View.INVISIBLE);
-                }
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-                // Hide the keyboard.
-                inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
+            case R.id.easyprivacy:
+                // Toggle the EasyPrivacy status.
+                easyPrivacyEnabled = !easyPrivacyEnabled;
 
-                // Check to see if Privacy Browser is waiting on Orbot.
-                if (!waitingForOrbot) {  // Process the URL.
-                    // The formatted URL string must be updated at the beginning of the load, so that if the user toggles JavaScript during the load the new website is reloaded.
-                    formattedUrlString = url;
+                // Update the menu checkbox.
+                menuItem.setChecked(easyPrivacyEnabled);
 
-                    // Display the formatted URL text.
-                    urlTextBox.setText(formattedUrlString);
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-                    // Apply text highlighting to `urlTextBox`.
-                    highlightUrlText();
+            case R.id.fanboys_annoyance_list:
+                // Toggle Fanboy's Annoyance List status.
+                fanboysAnnoyanceListEnabled = !fanboysAnnoyanceListEnabled;
 
-                    // Get a URI for the current URL.
-                    Uri currentUri = Uri.parse(formattedUrlString);
+                // Update the menu checkbox.
+                menuItem.setChecked(fanboysAnnoyanceListEnabled);
 
-                    // Get the IP addresses for the host.
-                    new GetHostIpAddresses(activity).execute(currentUri.getHost());
+                // Update the staus of Fanboy's Social Blocking List.
+                MenuItem fanboysSocialBlockingListMenuItem = mainMenu.findItem(R.id.fanboys_social_blocking_list);
+                fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListEnabled);
 
-                    // Apply any custom domain settings if the URL was loaded by navigating history.
-                    if (navigatingHistory) {
-                        // Apply the domain settings.
-                        boolean userAgentChanged = applyDomainSettings(url, true, false);
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-                        // Reset `navigatingHistory`.
-                        navigatingHistory = false;
+            case R.id.fanboys_social_blocking_list:
+                // Toggle Fanboy's Social Blocking List status.
+                fanboysSocialBlockingListEnabled = !fanboysSocialBlockingListEnabled;
 
-                        // Manually load the URL if the user agent has changed, which will have caused the previous URL to be reloaded.
-                        if (userAgentChanged) {
-                            loadUrl(formattedUrlString);
-                        }
-                    }
+                // Update the menu checkbox.
+                menuItem.setChecked(fanboysSocialBlockingListEnabled);
 
-                    // Replace Refresh with Stop if the menu item has been created.  (The WebView typically begins loading before the menu items are instantiated.)
-                    if (refreshMenuItem != null) {
-                        // Set the title.
-                        refreshMenuItem.setTitle(R.string.stop);
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-                        // If the icon is displayed in the AppBar, set it according to the theme.
-                        if (displayAdditionalAppBarIcons) {
-                            if (darkTheme) {
-                                refreshMenuItem.setIcon(R.drawable.close_dark);
-                            } else {
-                                refreshMenuItem.setIcon(R.drawable.close_light);
-                            }
-                        }
-                    }
-                }
-            }
+            case R.id.ultraprivacy:
+                // Toggle the UltraPrivacy status.
+                ultraPrivacyEnabled = !ultraPrivacyEnabled;
 
-            // It is necessary to update `formattedUrlString` and `urlTextBox` after the page finishes loading because the final URL can change during load.
-            @Override
-            public void onPageFinished(WebView view, String url) {
-                // Reset the wide view port if it has been turned off by the waiting for Orbot message.
-                if (!waitingForOrbot) {
-                    // Only use a wide view port if the URL starts with `http`, not for `file://` and `content://`.
-                    mainWebView.getSettings().setUseWideViewPort(url.startsWith("http"));
-                }
+                // Update the menu checkbox.
+                menuItem.setChecked(ultraPrivacyEnabled);
 
-                // Flush any cookies to persistent storage.  `CookieManager` has become very lazy about flushing cookies in recent versions.
-                if (firstPartyCookiesEnabled && Build.VERSION.SDK_INT >= 21) {
-                    cookieManager.flush();
-                }
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-                // Update the Refresh menu item if it has been created.
-                if (refreshMenuItem != null) {
-                    // Reset the Refresh title.
-                    refreshMenuItem.setTitle(R.string.refresh);
+            case R.id.block_all_third_party_requests:
+                //Toggle the third-party requests blocker status.
+                blockAllThirdPartyRequests = !blockAllThirdPartyRequests;
 
-                    // If the icon is displayed in the AppBar, reset it according to the theme.
-                    if (displayAdditionalAppBarIcons) {
-                        if (darkTheme) {
-                            refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
-                        } else {
-                            refreshMenuItem.setIcon(R.drawable.refresh_enabled_light);
-                        }
-                    }
-                }
+                // Update the menu checkbox.
+                menuItem.setChecked(blockAllThirdPartyRequests);
 
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
+            case R.id.user_agent_privacy_browser:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[0]);
 
-                // Clear the cache and history if Incognito Mode is enabled.
-                if (incognitoModeEnabled) {
-                    // Clear the cache.  `true` includes disk files.
-                    mainWebView.clearCache(true);
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-                    // Clear the back/forward history.
-                    mainWebView.clearHistory();
+            case R.id.user_agent_webview_default:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString("");
 
-                    // Manually delete cache folders.
-                    try {
-                        // Delete the main cache directory.
-                        privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache");
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-                        // Delete the secondary `Service Worker` cache directory.
-                        // A `String[]` must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
-                        privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
-                    } catch (IOException e) {
-                        // Do nothing if an error is thrown.
-                    }
-                }
+            case R.id.user_agent_firefox_on_android:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[2]);
 
-                // Update the URL text box and apply domain settings if not waiting on Orbot.
-                if (!waitingForOrbot) {
-                    // Check to see if `WebView` has set `url` to be `about:blank`.
-                    if (url.equals("about:blank")) {  // `WebView` is blank, so `formattedUrlString` should be `""` and `urlTextBox` should display a hint.
-                        // Set `formattedUrlString` to `""`.
-                        formattedUrlString = "";
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-                        urlTextBox.setText(formattedUrlString);
+            case R.id.user_agent_chrome_on_android:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[3]);
 
-                        // Request focus for `urlTextBox`.
-                        urlTextBox.requestFocus();
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-                        // Display the keyboard.
-                        inputMethodManager.showSoftInput(urlTextBox, 0);
+            case R.id.user_agent_safari_on_ios:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[4]);
 
-                        // Apply the domain settings.  This clears any settings from the previous domain.
-                        applyDomainSettings(formattedUrlString, true, false);
-                    } else {  // `WebView` has loaded a webpage.
-                        // Set the formatted URL string.  Getting the URL from the WebView instead of using the one provided by `onPageFinished` makes websites like YouTube function correctly.
-                        formattedUrlString = mainWebView.getUrl();
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-                        // Only update the URL text box if the user is not typing in it.
-                        if (!urlTextBox.hasFocus()) {
-                            // Display the formatted URL text.
-                            urlTextBox.setText(formattedUrlString);
+            case R.id.user_agent_firefox_on_linux:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[5]);
 
-                            // Apply text highlighting to `urlTextBox`.
-                            highlightUrlText();
-                        }
-                    }
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-                    // Store the SSL certificate so it can be accessed from `ViewSslCertificateDialog` and `PinnedMismatchDialog`.
-                    sslCertificate = mainWebView.getCertificate();
+            case R.id.user_agent_chromium_on_linux:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[6]);
 
-                    // Check the current website information against any pinned domain information if the current IP addresses have been loaded.
-                    if (!gettingIpAddresses) {
-                        checkPinnedMismatch();
-                    }
-                }
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-                // Reset `urlIsLoading`, which is used to prevent reloads on redirect if the user agent changes.  It is also used to determine when to check for pinned mismatches.
-                urlIsLoading = false;
-            }
+            case R.id.user_agent_firefox_on_windows:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[7]);
 
-            // Handle SSL Certificate errors.
-            @Override
-            public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
-                // Get the current website SSL certificate.
-                SslCertificate currentWebsiteSslCertificate = error.getCertificate();
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-                // Extract the individual pieces of information from the current website SSL certificate.
-                String currentWebsiteIssuedToCName = currentWebsiteSslCertificate.getIssuedTo().getCName();
-                String currentWebsiteIssuedToOName = currentWebsiteSslCertificate.getIssuedTo().getOName();
-                String currentWebsiteIssuedToUName = currentWebsiteSslCertificate.getIssuedTo().getUName();
-                String currentWebsiteIssuedByCName = currentWebsiteSslCertificate.getIssuedBy().getCName();
-                String currentWebsiteIssuedByOName = currentWebsiteSslCertificate.getIssuedBy().getOName();
-                String currentWebsiteIssuedByUName = currentWebsiteSslCertificate.getIssuedBy().getUName();
-                Date currentWebsiteSslStartDate = currentWebsiteSslCertificate.getValidNotBeforeDate();
-                Date currentWebsiteSslEndDate = currentWebsiteSslCertificate.getValidNotAfterDate();
+            case R.id.user_agent_chrome_on_windows:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[8]);
 
-                // Proceed to the website if the current SSL website certificate matches the pinned domain certificate.
-                if (pinnedSslCertificate &&
-                        currentWebsiteIssuedToCName.equals(pinnedSslIssuedToCName) && currentWebsiteIssuedToOName.equals(pinnedSslIssuedToOName) &&
-                        currentWebsiteIssuedToUName.equals(pinnedSslIssuedToUName) && currentWebsiteIssuedByCName.equals(pinnedSslIssuedByCName) &&
-                        currentWebsiteIssuedByOName.equals(pinnedSslIssuedByOName) && currentWebsiteIssuedByUName.equals(pinnedSslIssuedByUName) &&
-                        currentWebsiteSslStartDate.equals(pinnedSslStartDate) && currentWebsiteSslEndDate.equals(pinnedSslEndDate)) {
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-                    // An SSL certificate is pinned and matches the current domain certificate.  Proceed to the website without displaying an error.
-                    handler.proceed();
-                } else {  // Either there isn't a pinned SSL certificate or it doesn't match the current website certificate.
-                    // Store `handler` so it can be accesses from `onSslErrorCancel()` and `onSslErrorProceed()`.
-                    sslErrorHandler = handler;
+            case R.id.user_agent_edge_on_windows:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[9]);
 
-                    // Display the SSL error `AlertDialog`.
-                    DialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error);
-                    sslCertificateErrorDialogFragment.show(fragmentManager, getString(R.string.ssl_certificate_error));
-                }
-            }
-        });
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-        // Get the intent that started the app.
-        Intent launchingIntent = getIntent();
+            case R.id.user_agent_internet_explorer_on_windows:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[10]);
 
-        // Get the information from the intent.
-        String launchingIntentAction = launchingIntent.getAction();
-        Uri launchingIntentUriData = launchingIntent.getData();
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-        // If the intent action is a web search, perform the search.
-        if ((launchingIntentAction != null) && launchingIntentAction.equals(Intent.ACTION_WEB_SEARCH)) {
-            // Create an encoded URL string.
-            String encodedUrlString;
+            case R.id.user_agent_safari_on_macos:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[11]);
 
-            // Sanitize the search input and convert it to a search.
-            try {
-                encodedUrlString = URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8");
-            } catch (UnsupportedEncodingException exception) {
-                encodedUrlString = "";
-            }
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-            // Add the base search URL.
-            formattedUrlString = searchURL + encodedUrlString;
-        } else if (launchingIntentUriData != null){  // Check to see if the intent contains a new URL.
-            // Set the formatted URL string.
-            formattedUrlString = launchingIntentUriData.toString();
-        }
+            case R.id.user_agent_custom:
+                // Update the user agent.
+                currentWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
 
-        // Load the website if not waiting for Orbot to connect.
-        if (!waitingForOrbot) {
-            loadUrl(formattedUrlString);
-        }
-    }
+                // Reload the current WebView.
+                currentWebView.reload();
+                return true;
 
-    @Override
-    protected void onNewIntent(Intent intent) {
-        // Sets the new intent as the activity intent, so that any future `getIntent()`s pick up this one instead of creating a new activity.
-        setIntent(intent);
+            case R.id.font_size_twenty_five_percent:
+                currentWebView.getSettings().setTextZoom(25);
+                return true;
 
-        // Get the information from the intent.
-        String intentAction = intent.getAction();
-        Uri intentUriData = intent.getData();
+            case R.id.font_size_fifty_percent:
+                currentWebView.getSettings().setTextZoom(50);
+                return true;
 
-        // If the intent action is a web search, perform the search.
-        if ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH)) {
-            // Create an encoded URL string.
-            String encodedUrlString;
+            case R.id.font_size_seventy_five_percent:
+                currentWebView.getSettings().setTextZoom(75);
+                return true;
 
-            // Sanitize the search input and convert it to a search.
-            try {
-                encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8");
-            } catch (UnsupportedEncodingException exception) {
-                encodedUrlString = "";
-            }
+            case R.id.font_size_one_hundred_percent:
+                currentWebView.getSettings().setTextZoom(100);
+                return true;
 
-            // Add the base search URL.
-            formattedUrlString = searchURL + encodedUrlString;
-        } else if (intentUriData != null){  // Check to see if the intent contains a new URL.
-            // Set the formatted URL string.
-            formattedUrlString = intentUriData.toString();
-        }
+            case R.id.font_size_one_hundred_twenty_five_percent:
+                currentWebView.getSettings().setTextZoom(125);
+                return true;
 
-        // Load the URL.
-        loadUrl(formattedUrlString);
+            case R.id.font_size_one_hundred_fifty_percent:
+                currentWebView.getSettings().setTextZoom(150);
+                return true;
 
-        // Get a handle for the drawer layout.
-        DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
+            case R.id.font_size_one_hundred_seventy_five_percent:
+                currentWebView.getSettings().setTextZoom(175);
+                return true;
 
-        // Close the navigation drawer if it is open.
-        if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
-            drawerLayout.closeDrawer(GravityCompat.START);
-        }
+            case R.id.font_size_two_hundred_percent:
+                currentWebView.getSettings().setTextZoom(200);
+                return true;
 
-        // Close the bookmarks drawer if it is open.
-        if (drawerLayout.isDrawerVisible(GravityCompat.END)) {
-            drawerLayout.closeDrawer(GravityCompat.END);
-        }
+            case R.id.swipe_to_refresh:
+                // Get a handle for the swipe refresh layout.
+                SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
 
-        // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it.
-        mainWebView.requestFocus();
-    }
+                // Toggle swipe to refresh.
+                swipeRefreshLayout.setEnabled(!swipeRefreshLayout.isEnabled());
+                return true;
 
-    @Override
-    public void onRestart() {
-        // Run the default commands.
-        super.onRestart();
+            case R.id.display_images:
+                if (currentWebView.getSettings().getLoadsImagesAutomatically()) {  // Images are currently loaded automatically.
+                    // Disable loading of images.
+                    currentWebView.getSettings().setLoadsImagesAutomatically(false);
 
-        // Make sure Orbot is running if Privacy Browser is proxying through Orbot.
-        if (proxyThroughOrbot) {
-            // Request Orbot to start.  If Orbot is already running no hard will be caused by this request.
-            Intent orbotIntent = new Intent("org.torproject.android.intent.action.START");
+                    // Reload the website to remove existing images.
+                    currentWebView.reload();
+                } else {  // Images are not currently loaded automatically.
+                    // Enable loading of images.  Missing images will be loaded without the need for a reload.
+                    currentWebView.getSettings().setLoadsImagesAutomatically(true);
+                }
+                return true;
 
-            // Send the intent to the Orbot package.
-            orbotIntent.setPackage("org.torproject.android");
+            case R.id.night_mode:
+                // Toggle night mode.
+                nightMode = !nightMode;
 
-            // Make it so.
-            sendBroadcast(orbotIntent);
-        }
+                // Enable or disable JavaScript according to night mode, the global preference, and any domain settings.
+                if (nightMode) {  // Night mode is enabled.  Enable JavaScript.
+                    // Update the global variable.
+                    javaScriptEnabled = true;
+                } else if (domainSettingsApplied) {  // Night mode is disabled and domain settings are applied.  Set JavaScript according to the domain settings.
+                    // Get the JavaScript preference that was stored the last time domain settings were loaded.
+                    javaScriptEnabled = domainSettingsJavaScriptEnabled;
+                } else {  // Night mode is disabled and domain settings are not applied.  Set JavaScript according to the global preference.
+                    // Get a handle for the shared preference.
+                    SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
-        // Apply the app settings if returning from the Settings activity..
-        if (reapplyAppSettingsOnRestart) {
-            // Apply the app settings.
-            applyAppSettings();
+                    // Get the JavaScript preference.
+                    javaScriptEnabled = sharedPreferences.getBoolean("javascript", false);
+                }
 
-            // Reload the webpage if displaying of images has been disabled in the Settings activity.
-            if (reloadOnRestart) {
-                // Reload `mainWebView`.
-                mainWebView.reload();
+                // Apply the JavaScript setting to the WebView.
+                currentWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
 
-                // Reset `reloadOnRestartBoolean`.
-                reloadOnRestart = false;
-            }
+                // Update the privacy icons.
+                updatePrivacyIcons(false);
 
-            // Reset the return from settings flag.
-            reapplyAppSettingsOnRestart = false;
-        }
+                // Reload the website.
+                currentWebView.reload();
+                return true;
 
-        // Apply the domain settings if returning from the Domains activity.
-        if (reapplyDomainSettingsOnRestart) {
-            // Reapply the domain settings.
-            applyDomainSettings(formattedUrlString, false, true);
+            case R.id.find_on_page:
+                // Get a handle for the views.
+                Toolbar toolbar = findViewById(R.id.toolbar);
+                LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
 
-            // Reset `reapplyDomainSettingsOnRestart`.
-            reapplyDomainSettingsOnRestart = false;
-        }
+                // Hide the toolbar.
+                toolbar.setVisibility(View.GONE);
 
-        // Load the URL on restart to apply changes to night mode.
-        if (loadUrlOnRestart) {
-            // Load the current `formattedUrlString`.
-            loadUrl(formattedUrlString);
+                // Show the find on page linear layout.
+                findOnPageLinearLayout.setVisibility(View.VISIBLE);
 
-            // Reset `loadUrlOnRestart.
-            loadUrlOnRestart = false;
-        }
+                // Display the keyboard.  The app must wait 200 ms before running the command to work around a bug in Android.
+                // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
+                findOnPageEditText.postDelayed(() -> {
+                    // Set the focus on `findOnPageEditText`.
+                    findOnPageEditText.requestFocus();
 
-        // Update the bookmarks drawer if returning from the Bookmarks activity.
-        if (restartFromBookmarksActivity) {
-            // Get a handle for the drawer layout.
-            DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
+                    // Display the keyboard.  `0` sets no input flags.
+                    inputMethodManager.showSoftInput(findOnPageEditText, 0);
+                }, 200);
+                return true;
 
-            // Close the bookmarks drawer.
-            drawerLayout.closeDrawer(GravityCompat.END);
+            case R.id.view_source:
+                // Launch the View Source activity.
+                Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
+                startActivity(viewSourceIntent);
+                return true;
 
-            // Reload the bookmarks drawer.
-            loadBookmarksFolder();
+            case R.id.share_url:
+                // Setup the share string.
+                String shareString = webViewTitle + " – " + urlTextBox.getText().toString();
 
-            // Reset `restartFromBookmarksActivity`.
-            restartFromBookmarksActivity = false;
-        }
+                // Create the share intent.
+                Intent shareIntent = new Intent(Intent.ACTION_SEND);
+                shareIntent.putExtra(Intent.EXTRA_TEXT, shareString);
+                shareIntent.setType("text/plain");
 
-        // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.  This can be important if the screen was rotated.
-        updatePrivacyIcons(true);
-    }
+                // Make it so.
+                startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url)));
+                return true;
 
-    // `onResume()` runs after `onStart()`, which runs after `onCreate()` and `onRestart()`.
-    @Override
-    public void onResume() {
-        // Run the default commands.
-        super.onResume();
+            case R.id.print:
+                // Get a `PrintManager` instance.
+                PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
 
-        // Resume JavaScript (if enabled).
-        mainWebView.resumeTimers();
+                // Create a print document adapter form the current WebView.
+                PrintDocumentAdapter printDocumentAdapter = currentWebView.createPrintDocumentAdapter();
 
-        // Resume `mainWebView`.
-        mainWebView.onResume();
+                // Remove the lint error below that `printManager` might be `null`.
+                assert printManager != null;
 
-        // Display a message to the user if waiting for Orbot.
-        if (waitingForOrbot && !orbotStatus.equals("ON")) {
-            // Disable the wide view port so that the waiting for Orbot text is displayed correctly.
-            mainWebView.getSettings().setUseWideViewPort(false);
+                // Print the document.  The print attributes are `null`.
+                printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
+                return true;
+
+            case R.id.open_with_app:
+                openWithApp(formattedUrlString);
+                return true;
 
-            // Load a waiting page.  `null` specifies no encoding, which defaults to ASCII.
-            mainWebView.loadData(waitingForOrbotHtmlString, "text/html", null);
-        }
+            case R.id.open_with_browser:
+                openWithBrowser(formattedUrlString);
+                return true;
 
-        if (displayingFullScreenVideo || inFullScreenBrowsingMode) {
-            // Get a handle for the root frame layouts.
-            FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
+            case R.id.add_to_homescreen:
+                // Instantiate the create home screen shortcut dialog.
+                DialogFragment createHomeScreenShortcutDialogFragment = CreateHomeScreenShortcutDialog.createDialog(currentWebView.getTitle(), formattedUrlString, favoriteIconBitmap);
 
-            // Remove the translucent status flag.  This is necessary so the root frame layout can fill the entire screen.
-            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+                // Show the create home screen shortcut dialog.
+                createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
+                return true;
 
-            /* Hide the system bars.
-             * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
-             * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
-             * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
-             * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
-             */
-            rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
-                    View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
-        } else if (BuildConfig.FLAVOR.contentEquals("free")) {  // Resume the adView for the free flavor.
-            // Resume the ad.
-            AdHelper.resumeAd(findViewById(R.id.adview));
-        }
-    }
+            case R.id.proxy_through_orbot:
+                // Toggle the proxy through Orbot variable.
+                proxyThroughOrbot = !proxyThroughOrbot;
 
-    @Override
-    public void onPause() {
-        // Run the default commands.
-        super.onPause();
+                // Apply the proxy through Orbot settings.
+                applyProxyThroughOrbot(true);
+                return true;
 
-        // Pause `mainWebView`.
-        mainWebView.onPause();
+            case R.id.refresh:
+                if (menuItem.getTitle().equals(getString(R.string.refresh))) {  // The refresh button was pushed.
+                    // Reload the current WebView.
+                    currentWebView.reload();
+                } else {  // The stop button was pushed.
+                    // Stop the loading of the WebView.
+                    currentWebView.stopLoading();
+                }
+                return true;
 
-        // Stop all JavaScript.
-        mainWebView.pauseTimers();
+            case R.id.ad_consent:
+                // Display the ad consent dialog.
+                DialogFragment adConsentDialogFragment = new AdConsentDialog();
+                adConsentDialogFragment.show(getSupportFragmentManager(), getString(R.string.ad_consent));
+                return true;
 
-        // Pause the ad or it will continue to consume resources in the background on the free flavor.
-        if (BuildConfig.FLAVOR.contentEquals("free")) {
-            // Pause the ad.
-            AdHelper.pauseAd(findViewById(R.id.adview));
+            default:
+                // Don't consume the event.
+                return super.onOptionsItemSelected(menuItem);
         }
     }
 
+    // removeAllCookies is deprecated, but it is required for API < 21.
+    @SuppressWarnings("deprecation")
     @Override
-    public void onDestroy() {
-        // Unregister the Orbot status broadcast receiver.
-        this.unregisterReceiver(orbotStatusBroadcastReceiver);
+    public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
+        // Get the menu item ID.
+        int menuItemId = menuItem.getItemId();
 
-        // Close the bookmarks cursor and database.
-        bookmarksCursor.close();
-        bookmarksDatabaseHelper.close();
+        // Run the commands that correspond to the selected menu item.
+        switch (menuItemId) {
+            case R.id.close_tab:
+                // Get a handle for the tab layout.
+                TabLayout tabLayout = findViewById(R.id.tablayout);
 
-        // Run the default commands.
-        super.onDestroy();
-    }
+                // Get the current tab number.
+                int currentTabNumber = tabLayout.getSelectedTabPosition();
 
-    @Override
-    public boolean onCreateOptionsMenu(Menu menu) {
-        // Inflate the menu.  This adds items to the action bar if it is present.
-        getMenuInflater().inflate(R.menu.webview_options_menu, menu);
+                // Delete the tab and page.
+                webViewPagerAdapter.deletePage(currentTabNumber);
+                break;
 
-        // Set mainMenu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons`.
-        mainMenu = menu;
+            case R.id.clear_and_exit:
+                // Close the bookmarks cursor and database.
+                bookmarksCursor.close();
+                bookmarksDatabaseHelper.close();
 
-        // Set the initial status of the privacy icons.  `false` does not call `invalidateOptionsMenu` as the last step.
-        updatePrivacyIcons(false);
+                // Get a handle for the shared preferences.
+                SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
-        // Get handles for the menu items.
-        MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
-        MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
-        MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
-        MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data);  // Form data can be removed once the minimum API >= 26.
-        MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data);  // Form data can be removed once the minimum API >= 26.
-        refreshMenuItem = menu.findItem(R.id.refresh);
-        blocklistsMenuItem = menu.findItem(R.id.blocklists);
-        easyListMenuItem = menu.findItem(R.id.easylist);
-        easyPrivacyMenuItem = menu.findItem(R.id.easyprivacy);
-        fanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list);
-        fanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list);
-        ultraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy);
-        blockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
-        MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent);
+                // Get the status of the clear everything preference.
+                boolean clearEverything = sharedPreferences.getBoolean("clear_everything", true);
 
-        // Only display third-party cookies if API >= 21
-        toggleThirdPartyCookiesMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
+                // Clear cookies.
+                if (clearEverything || sharedPreferences.getBoolean("clear_cookies", true)) {
+                    // The command to remove cookies changed slightly in API 21.
+                    if (Build.VERSION.SDK_INT >= 21) {
+                        cookieManager.removeAllCookies(null);
+                    } else {
+                        cookieManager.removeAllCookie();
+                    }
 
-        // Only display the form data menu items if the API < 26.
-        toggleSaveFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
-        clearFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
+                    // Manually delete the cookies database, as `CookieManager` sometimes will not flush its changes to disk before `System.exit(0)` is run.
+                    try {
+                        // Two commands must be used because `Runtime.exec()` does not like `*`.
+                        Process deleteCookiesProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies");
+                        Process deleteCookiesJournalProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies-journal");
 
-        // Only show Ad Consent if this is the free flavor.
-        adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free"));
+                        // Wait until the processes have finished.
+                        deleteCookiesProcess.waitFor();
+                        deleteCookiesJournalProcess.waitFor();
+                    } catch (Exception exception) {
+                        // Do nothing if an error is thrown.
+                    }
+                }
 
-        // Get the shared preference values.
-        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+                // Clear DOM storage.
+                if (clearEverything || sharedPreferences.getBoolean("clear_dom_storage", true)) {
+                    // Ask `WebStorage` to clear the DOM storage.
+                    WebStorage webStorage = WebStorage.getInstance();
+                    webStorage.deleteAllData();
 
-        // Get the status of the additional AppBar icons.
-        displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false);
+                    // Manually delete the DOM storage files and directories, as `WebStorage` sometimes will not flush its changes to disk before `System.exit(0)` is run.
+                    try {
+                        // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
+                        Process deleteLocalStorageProcess = privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
 
-        // Set the status of the additional app bar icons.  Setting the refresh menu item to `SHOW_AS_ACTION_ALWAYS` makes it appear even on small devices like phones.
-        if (displayAdditionalAppBarIcons) {
-            toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
-            toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
-            refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
-        } else { //Do not display the additional icons.
-            toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
-            toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
-            refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
-        }
+                        // Multiple commands must be used because `Runtime.exec()` does not like `*`.
+                        Process deleteIndexProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
+                        Process deleteQuotaManagerProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
+                        Process deleteQuotaManagerJournalProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
+                        Process deleteDatabaseProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
 
-        // Replace Refresh with Stop if a URL is already loading.
-        if (urlIsLoading) {
-            // Set the title.
-            refreshMenuItem.setTitle(R.string.stop);
+                        // Wait until the processes have finished.
+                        deleteLocalStorageProcess.waitFor();
+                        deleteIndexProcess.waitFor();
+                        deleteQuotaManagerProcess.waitFor();
+                        deleteQuotaManagerJournalProcess.waitFor();
+                        deleteDatabaseProcess.waitFor();
+                    } catch (Exception exception) {
+                        // Do nothing if an error is thrown.
+                    }
+                }
 
-            // If the icon is displayed in the AppBar, set it according to the theme.
-            if (displayAdditionalAppBarIcons) {
-                if (darkTheme) {
-                    refreshMenuItem.setIcon(R.drawable.close_dark);
-                } else {
-                    refreshMenuItem.setIcon(R.drawable.close_light);
+                // Clear form data if the API < 26.
+                if ((Build.VERSION.SDK_INT < 26) && (clearEverything || sharedPreferences.getBoolean("clear_form_data", true))) {
+                    WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
+                    webViewDatabase.clearFormData();
+
+                    // Manually delete the form data database, as `WebViewDatabase` sometimes will not flush its changes to disk before `System.exit(0)` is run.
+                    try {
+                        // A string array must be used because the database contains a space and `Runtime.exec` will not otherwise escape the string correctly.
+                        Process deleteWebDataProcess = privacyBrowserRuntime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data"});
+                        Process deleteWebDataJournalProcess = privacyBrowserRuntime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data-journal"});
+
+                        // Wait until the processes have finished.
+                        deleteWebDataProcess.waitFor();
+                        deleteWebDataJournalProcess.waitFor();
+                    } catch (Exception exception) {
+                        // Do nothing if an error is thrown.
+                    }
                 }
-            }
-        }
 
-        return true;
-    }
+                // Clear the cache.
+                if (clearEverything || sharedPreferences.getBoolean("clear_cache", true)) {
+                    // Clear the cache.
+                    // TODO
+                    currentWebView.clearCache(true);
 
-    @Override
-    public boolean onPrepareOptionsMenu(Menu menu) {
-        // Get a handle for the swipe refresh layout.
-        SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
+                    // Manually delete the cache directories.
+                    try {
+                        // Delete the main cache directory.
+                        Process deleteCacheProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache");
 
-        // Get handles for the menu items.
-        MenuItem addOrEditDomain = menu.findItem(R.id.add_or_edit_domain);
-        MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
-        MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
-        MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
-        MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data);  // Form data can be removed once the minimum API >= 26.
-        MenuItem clearDataMenuItem = menu.findItem(R.id.clear_data);
-        MenuItem clearCookiesMenuItem = menu.findItem(R.id.clear_cookies);
-        MenuItem clearDOMStorageMenuItem = menu.findItem(R.id.clear_dom_storage);
-        MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data);  // Form data can be removed once the minimum API >= 26.
-        MenuItem fontSizeMenuItem = menu.findItem(R.id.font_size);
-        MenuItem swipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh);
-        MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images);
-        MenuItem nightModeMenuItem = menu.findItem(R.id.night_mode);
-        MenuItem proxyThroughOrbotMenuItem = menu.findItem(R.id.proxy_through_orbot);
+                        // Delete the secondary `Service Worker` cache directory.
+                        // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
+                        Process deleteServiceWorkerProcess = privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
 
-        // Set the text for the domain menu item.
-        if (domainSettingsApplied) {
-            addOrEditDomain.setTitle(R.string.edit_domain_settings);
-        } else {
-            addOrEditDomain.setTitle(R.string.add_domain_settings);
-        }
+                        // Wait until the processes have finished.
+                        deleteCacheProcess.waitFor();
+                        deleteServiceWorkerProcess.waitFor();
+                    } catch (Exception exception) {
+                        // Do nothing if an error is thrown.
+                    }
+                }
 
-        // Set the status of the menu item checkboxes.
-        toggleFirstPartyCookiesMenuItem.setChecked(firstPartyCookiesEnabled);
-        toggleThirdPartyCookiesMenuItem.setChecked(thirdPartyCookiesEnabled);
-        toggleDomStorageMenuItem.setChecked(domStorageEnabled);
-        toggleSaveFormDataMenuItem.setChecked(saveFormDataEnabled);  // Form data can be removed once the minimum API >= 26.
-        easyListMenuItem.setChecked(easyListEnabled);
-        easyPrivacyMenuItem.setChecked(easyPrivacyEnabled);
-        fanboysAnnoyanceListMenuItem.setChecked(fanboysAnnoyanceListEnabled);
-        fanboysSocialBlockingListMenuItem.setChecked(fanboysSocialBlockingListEnabled);
-        ultraPrivacyMenuItem.setChecked(ultraPrivacyEnabled);
-        blockAllThirdPartyRequestsMenuItem.setChecked(blockAllThirdPartyRequests);
-        swipeToRefreshMenuItem.setChecked(swipeRefreshLayout.isEnabled());
-        displayImagesMenuItem.setChecked(mainWebView.getSettings().getLoadsImagesAutomatically());
-        nightModeMenuItem.setChecked(nightMode);
-        proxyThroughOrbotMenuItem.setChecked(proxyThroughOrbot);
+                // Clear SSL certificate preferences.
+                // TODO
+                currentWebView.clearSslPreferences();
 
-        // Enable third-party cookies if first-party cookies are enabled.
-        toggleThirdPartyCookiesMenuItem.setEnabled(firstPartyCookiesEnabled);
+                // Clear the back/forward history.
+                // TODO
+                currentWebView.clearHistory();
 
-        // Enable DOM Storage if JavaScript is enabled.
-        toggleDomStorageMenuItem.setEnabled(javaScriptEnabled);
+                // Clear `formattedUrlString`.
+                formattedUrlString = null;
 
-        // Enable Clear Cookies if there are any.
-        clearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
+                // Clear `customHeaders`.
+                customHeaders.clear();
 
-        // Get a count of the number of files in the Local Storage directory.
-        File localStorageDirectory = new File (privateDataDirectoryString + "/app_webview/Local Storage/");
-        int localStorageDirectoryNumberOfFiles = 0;
-        if (localStorageDirectory.exists()) {
-            localStorageDirectoryNumberOfFiles = localStorageDirectory.list().length;
-        }
+                // Destroy the internal state of `mainWebView`.
+                // TODO
+                currentWebView.destroy();
 
-        // Get a count of the number of files in the IndexedDB directory.
-        File indexedDBDirectory = new File (privateDataDirectoryString + "/app_webview/IndexedDB");
-        int indexedDBDirectoryNumberOfFiles = 0;
-        if (indexedDBDirectory.exists()) {
-            indexedDBDirectoryNumberOfFiles = indexedDBDirectory.list().length;
-        }
+                // Manually delete the `app_webview` folder, which contains the cookies, DOM storage, form data, and `Service Worker` cache.
+                // See `https://code.google.com/p/android/issues/detail?id=233826&thanks=233826&ts=1486670530`.
+                if (clearEverything) {
+                    try {
+                        // Delete the folder.
+                        Process deleteAppWebviewProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview");
 
-        // Enable Clear DOM Storage if there is any.
-        clearDOMStorageMenuItem.setEnabled(localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0);
+                        // Wait until the process has finished.
+                        deleteAppWebviewProcess.waitFor();
+                    } catch (Exception exception) {
+                        // Do nothing if an error is thrown.
+                    }
+                }
 
-        // Enable Clear Form Data is there is any.  This can be removed once the minimum API >= 26.
-        if (Build.VERSION.SDK_INT < 26) {
-            WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
-            clearFormDataMenuItem.setEnabled(mainWebViewDatabase.hasFormData());
-        } else {
-            // Disable clear form data because it is not supported on current version of Android.
-            clearFormDataMenuItem.setEnabled(false);
-        }
+                // Close Privacy Browser.  `finishAndRemoveTask` also removes Privacy Browser from the recent app list.
+                if (Build.VERSION.SDK_INT >= 21) {
+                    finishAndRemoveTask();
+                } else {
+                    finish();
+                }
 
-        // Enable Clear Data if any of the submenu items are enabled.
-        clearDataMenuItem.setEnabled(clearCookiesMenuItem.isEnabled() || clearDOMStorageMenuItem.isEnabled() || clearFormDataMenuItem.isEnabled());
+                // Remove the terminated program from RAM.  The status code is `0`.
+                System.exit(0);
+                break;
 
-        // Disable Fanboy's Social Blocking List if Fanboy's Annoyance List is checked.
-        fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListEnabled);
+            case R.id.home:
+                loadUrl(homepage);
+                break;
 
-        // Initialize the display names for the blocklists with the number of blocked requests.
-        blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + blockedRequests);
-        easyListMenuItem.setTitle(easyListBlockedRequests + " - " + getString(R.string.easylist));
-        easyPrivacyMenuItem.setTitle(easyPrivacyBlockedRequests + " - " + getString(R.string.easyprivacy));
-        fanboysAnnoyanceListMenuItem.setTitle(fanboysAnnoyanceListBlockedRequests + " - " + getString(R.string.fanboys_annoyance_list));
-        fanboysSocialBlockingListMenuItem.setTitle(fanboysSocialBlockingListBlockedRequests + " - " + getString(R.string.fanboys_social_blocking_list));
-        ultraPrivacyMenuItem.setTitle(ultraPrivacyBlockedRequests + " - " + getString(R.string.ultraprivacy));
-        blockAllThirdPartyRequestsMenuItem.setTitle(thirdPartyBlockedRequests + " - " + getString(R.string.block_all_third_party_requests));
+            case R.id.back:
+                if (currentWebView.canGoBack()) {
+                    // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
+                    formattedUrlString = "";
 
-        // Get the current user agent.
-        String currentUserAgent = mainWebView.getSettings().getUserAgentString();
+                    // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
+                    navigatingHistory = true;
 
-        // Select the current user agent menu item.  A switch statement cannot be used because the user agents are not compile time constants.
-        if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[0])) {  // Privacy Browser.
-            menu.findItem(R.id.user_agent_privacy_browser).setChecked(true);
-        } else if (currentUserAgent.equals(webViewDefaultUserAgent)) {  // WebView Default.
-            menu.findItem(R.id.user_agent_webview_default).setChecked(true);
-        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[2])) {  // Firefox on Android.
-            menu.findItem(R.id.user_agent_firefox_on_android).setChecked(true);
-        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[3])) {  // Chrome on Android.
-            menu.findItem(R.id.user_agent_chrome_on_android).setChecked(true);
-        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[4])) {  // Safari on iOS.
-            menu.findItem(R.id.user_agent_safari_on_ios).setChecked(true);
-        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[5])) {  // Firefox on Linux.
-            menu.findItem(R.id.user_agent_firefox_on_linux).setChecked(true);
-        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[6])) {  // Chromium on Linux.
-            menu.findItem(R.id.user_agent_chromium_on_linux).setChecked(true);
-        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[7])) {  // Firefox on Windows.
-            menu.findItem(R.id.user_agent_firefox_on_windows).setChecked(true);
-        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[8])) {  // Chrome on Windows.
-            menu.findItem(R.id.user_agent_chrome_on_windows).setChecked(true);
-        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[9])) {  // Edge on Windows.
-            menu.findItem(R.id.user_agent_edge_on_windows).setChecked(true);
-        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[10])) {  // Internet Explorer on Windows.
-            menu.findItem(R.id.user_agent_internet_explorer_on_windows).setChecked(true);
-        } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[11])) {  // Safari on macOS.
-            menu.findItem(R.id.user_agent_safari_on_macos).setChecked(true);
-        } else {  // Custom user agent.
-            menu.findItem(R.id.user_agent_custom).setChecked(true);
-        }
+                    // Load the previous website in the history.
+                    currentWebView.goBack();
+                }
+                break;
 
-        // Initialize font size variables.
-        int fontSize = mainWebView.getSettings().getTextZoom();
-        String fontSizeTitle;
-        MenuItem selectedFontSizeMenuItem;
+            case R.id.forward:
+                if (currentWebView.canGoForward()) {
+                    // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
+                    formattedUrlString = "";
 
-        // Prepare the font size title and current size menu item.
-        switch (fontSize) {
-            case 25:
-                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.twenty_five_percent);
-                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_twenty_five_percent);
+                    // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
+                    navigatingHistory = true;
+
+                    // Load the next website in the history.
+                    currentWebView.goForward();
+                }
                 break;
 
-            case 50:
-                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.fifty_percent);
-                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_fifty_percent);
+            case R.id.history:
+                // Get the `WebBackForwardList`.
+                WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
+
+                // Show the URL history dialog and name this instance `R.string.history`.
+                DialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(this, webBackForwardList);
+                urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history));
                 break;
 
-            case 75:
-                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.seventy_five_percent);
-                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_seventy_five_percent);
+            case R.id.requests:
+                // Launch the requests activity.
+                Intent requestsIntent = new Intent(this, RequestsActivity.class);
+                startActivity(requestsIntent);
                 break;
 
-            case 100:
-                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
-                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
+            case R.id.downloads:
+                // Launch the system Download Manager.
+                Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
+
+                // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list.
+                downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+                startActivity(downloadManagerIntent);
                 break;
 
-            case 125:
-                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_twenty_five_percent);
-                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_twenty_five_percent);
+            case R.id.domains:
+                // Set the flag to reapply the domain settings on restart when returning from Domain Settings.
+                reapplyDomainSettingsOnRestart = true;
+                currentDomainName = "";
+
+                // Launch the domains activity.
+                Intent domainsIntent = new Intent(this, DomainsActivity.class);
+                startActivity(domainsIntent);
                 break;
 
-            case 150:
-                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_fifty_percent);
-                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_fifty_percent);
+            case R.id.settings:
+                // Set the flag to reapply app settings on restart when returning from Settings.
+                reapplyAppSettingsOnRestart = true;
+
+                // Set the flag to reapply the domain settings on restart when returning from Settings.
+                reapplyDomainSettingsOnRestart = true;
+                currentDomainName = "";
+
+                // Launch the settings activity.
+                Intent settingsIntent = new Intent(this, SettingsActivity.class);
+                startActivity(settingsIntent);
                 break;
 
-            case 175:
-                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_seventy_five_percent);
-                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_seventy_five_percent);
+            case R.id.import_export:
+                // Launch the import/export activity.
+                Intent importExportIntent = new Intent (this, ImportExportActivity.class);
+                startActivity(importExportIntent);
                 break;
 
-            case 200:
-                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.two_hundred_percent);
-                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_two_hundred_percent);
+            case R.id.logcat:
+                // Launch the logcat activity.
+                Intent logcatIntent = new Intent(this, LogcatActivity.class);
+                startActivity(logcatIntent);
                 break;
 
-            default:
-                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
-                selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
+            case R.id.guide:
+                // Launch `GuideActivity`.
+                Intent guideIntent = new Intent(this, GuideActivity.class);
+                startActivity(guideIntent);
                 break;
-        }
 
-        // Set the font size title and select the current size menu item.
-        fontSizeMenuItem.setTitle(fontSizeTitle);
-        selectedFontSizeMenuItem.setChecked(true);
+            case R.id.about:
+                // Launch `AboutActivity`.
+                Intent aboutIntent = new Intent(this, AboutActivity.class);
+                startActivity(aboutIntent);
+                break;
+        }
 
-        // Run all the other default commands.
-        super.onPrepareOptionsMenu(menu);
+        // Get a handle for the drawer layout.
+        DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
 
-        // Display the menu.
+        // Close the navigation drawer.
+        drawerLayout.closeDrawer(GravityCompat.START);
         return true;
     }
 
     @Override
-    // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
-    @SuppressLint("SetJavaScriptEnabled")
-    // removeAllCookies is deprecated, but it is required for API < 21.
-    @SuppressWarnings("deprecation")
-    public boolean onOptionsItemSelected(MenuItem menuItem) {
-        // Reenter full screen browsing mode if it was interrupted by the options menu.  <https://redmine.stoutner.com/issues/389>
-        if (inFullScreenBrowsingMode) {
-            // Remove the translucent status flag.  This is necessary so the root frame layout can fill the entire screen.
-            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+    public void onPostCreate(Bundle savedInstanceState) {
+        // Run the default commands.
+        super.onPostCreate(savedInstanceState);
+
+        // Sync the state of the DrawerToggle after the default `onRestoreInstanceState()` has finished.  This creates the navigation drawer icon.
+        actionBarDrawerToggle.syncState();
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        // Run the default commands.
+        super.onConfigurationChanged(newConfig);
+
+        // Get the status bar pixel size.
+        int statusBarResourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
+        int statusBarPixelSize = getResources().getDimensionPixelSize(statusBarResourceId);
+
+        // Get the resource density.
+        float screenDensity = getResources().getDisplayMetrics().density;
+
+        // Recalculate the drawer header padding.
+        drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
+        drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
+        drawerHeaderPaddingBottom = (int) (8 * screenDensity);
+
+        // Reload the ad for the free flavor if not in full screen mode.
+        if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
+            // Reload the ad.  The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
+            AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
+        }
+
+        // `invalidateOptionsMenu` should recalculate the number of action buttons from the menu to display on the app bar, but it doesn't because of the this bug:
+        // https://code.google.com/p/android/issues/detail?id=20493#c8
+        // ActivityCompat.invalidateOptionsMenu(this);
+    }
 
-            FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
+    @Override
+    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
+        // Store the `HitTestResult`.
+        final WebView.HitTestResult hitTestResult = currentWebView.getHitTestResult();
 
-            /* Hide the system bars.
-             * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
-             * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
-             * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
-             * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
-             */
-            rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
-                    View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
-        }
+        // Create strings.
+        final String imageUrl;
+        final String linkUrl;
 
-        // Get the selected menu item ID.
-        int menuItemId = menuItem.getItemId();
+        // Get a handle for the the clipboard and fragment managers.
+        final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
+        FragmentManager fragmentManager = getSupportFragmentManager();
 
-        // Run the commands that correlate to the selected menu item.
-        switch (menuItemId) {
-            case R.id.toggle_javascript:
-                // Switch the status of javaScriptEnabled.
-                javaScriptEnabled = !javaScriptEnabled;
+        // Remove the lint errors below that `clipboardManager` might be `null`.
+        assert clipboardManager != null;
 
-                // Apply the new JavaScript status.
-                mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
+        switch (hitTestResult.getType()) {
+            // `SRC_ANCHOR_TYPE` is a link.
+            case WebView.HitTestResult.SRC_ANCHOR_TYPE:
+                // Get the target URL.
+                linkUrl = hitTestResult.getExtra();
 
-                // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
-                updatePrivacyIcons(true);
+                // Set the target URL as the title of the `ContextMenu`.
+                menu.setHeaderTitle(linkUrl);
 
-                // Display a `Snackbar`.
-                if (javaScriptEnabled) {  // JavaScrip is enabled.
-                    Snackbar.make(findViewById(R.id.main_webview), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
-                } else if (firstPartyCookiesEnabled) {  // JavaScript is disabled, but first-party cookies are enabled.
-                    Snackbar.make(findViewById(R.id.main_webview), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
-                } else {  // Privacy mode.
-                    Snackbar.make(findViewById(R.id.main_webview), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
-                }
+                // Add a Load URL entry.
+                menu.add(R.string.load_url).setOnMenuItemClickListener((MenuItem item) -> {
+                    loadUrl(linkUrl);
+                    return false;
+                });
 
-                // Reload the WebView.
-                mainWebView.reload();
-                return true;
+                // Add a Copy URL entry.
+                menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
+                    // Save the link URL in a `ClipData`.
+                    ClipData srcAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
 
-            case R.id.add_or_edit_domain:
-                if (domainSettingsApplied) {  // Edit the current domain settings.
-                    // Reapply the domain settings on returning to `MainWebViewActivity`.
-                    reapplyDomainSettingsOnRestart = true;
-                    currentDomainName = "";
+                    // Set the `ClipData` as the clipboard's primary clip.
+                    clipboardManager.setPrimaryClip(srcAnchorTypeClipData);
+                    return false;
+                });
 
-                    // Create an intent to launch the domains activity.
-                    Intent domainsIntent = new Intent(this, DomainsActivity.class);
+                // Add a Download URL entry.
+                menu.add(R.string.download_url).setOnMenuItemClickListener((MenuItem item) -> {
+                    // Check if the download should be processed by an external app.
+                    if (downloadWithExternalApp) {  // Download with an external app.
+                        openUrlWithExternalApp(linkUrl);
+                    } else {  // Download with Android's download manager.
+                        // Check to see if the storage permission has already been granted.
+                        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {  // The storage permission needs to be requested.
+                            // Store the variables for future use by `onRequestPermissionsResult()`.
+                            downloadUrl = linkUrl;
+                            downloadContentDisposition = "none";
+                            downloadContentLength = -1;
 
-                    // Put extra information instructing the domains activity to directly load the current domain and close on back instead of returning to the domains list.
-                    domainsIntent.putExtra("loadDomain", domainSettingsDatabaseId);
-                    domainsIntent.putExtra("closeOnBack", true);
+                            // Show a dialog if the user has previously denied the permission.
+                            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
+                                // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
+                                DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
 
-                    // Make it so.
-                    startActivity(domainsIntent);
-                } else {  // Add a new domain.
-                    // Apply the new domain settings on returning to `MainWebViewActivity`.
-                    reapplyDomainSettingsOnRestart = true;
-                    currentDomainName = "";
+                                // Show the download location permission alert dialog.  The permission will be requested when the the dialog is closed.
+                                downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
+                            } else {  // Show the permission request directly.
+                                // Request the permission.  The download dialog will be launched by `onRequestPermissionResult()`.
+                                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
+                            }
+                        } else {  // The storage permission has already been granted.
+                            // Get a handle for the download file alert dialog.
+                            DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(linkUrl, "none", -1);
 
-                    // Get the current domain
-                    Uri currentUri = Uri.parse(formattedUrlString);
-                    String currentDomain = currentUri.getHost();
+                            // Show the download file alert dialog.
+                            downloadFileDialogFragment.show(fragmentManager, getString(R.string.download));
+                        }
+                    }
+                    return false;
+                });
 
-                    // Initialize the database handler.  The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
-                    DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
+                // Add an Open with App entry.
+                menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
+                    openWithApp(linkUrl);
+                    return false;
+                });
 
-                    // Create the domain and store the database ID.
-                    int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain);
+                // Add an Open with Browser entry.
+                menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
+                    openWithBrowser(linkUrl);
+                    return false;
+                });
 
-                    // Create an intent to launch the domains activity.
-                    Intent domainsIntent = new Intent(this, DomainsActivity.class);
+                // Add a Cancel entry, which by default closes the context menu.
+                menu.add(R.string.cancel);
+                break;
 
-                    // Put extra information instructing the domains activity to directly load the new domain and close on back instead of returning to the domains list.
-                    domainsIntent.putExtra("loadDomain", newDomainDatabaseId);
-                    domainsIntent.putExtra("closeOnBack", true);
+            case WebView.HitTestResult.EMAIL_TYPE:
+                // Get the target URL.
+                linkUrl = hitTestResult.getExtra();
 
-                    // Make it so.
-                    startActivity(domainsIntent);
-                }
-                return true;
+                // Set the target URL as the title of the `ContextMenu`.
+                menu.setHeaderTitle(linkUrl);
 
-            case R.id.toggle_first_party_cookies:
-                // Switch the status of firstPartyCookiesEnabled.
-                firstPartyCookiesEnabled = !firstPartyCookiesEnabled;
+                // Add a Write Email entry.
+                menu.add(R.string.write_email).setOnMenuItemClickListener(item -> {
+                    // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
+                    Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
 
-                // Update the menu checkbox.
-                menuItem.setChecked(firstPartyCookiesEnabled);
+                    // Parse the url and set it as the data for the `Intent`.
+                    emailIntent.setData(Uri.parse("mailto:" + linkUrl));
 
-                // Apply the new cookie status.
-                cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
+                    // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
+                    emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
-                // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
-                updatePrivacyIcons(true);
+                    // Make it so.
+                    startActivity(emailIntent);
+                    return false;
+                });
 
-                // Display a `Snackbar`.
-                if (firstPartyCookiesEnabled) {  // First-party cookies are enabled.
-                    Snackbar.make(findViewById(R.id.main_webview), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
-                } else if (javaScriptEnabled) {  // JavaScript is still enabled.
-                    Snackbar.make(findViewById(R.id.main_webview), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
-                } else {  // Privacy mode.
-                    Snackbar.make(findViewById(R.id.main_webview), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
-                }
+                // Add a Copy Email Address entry.
+                menu.add(R.string.copy_email_address).setOnMenuItemClickListener(item -> {
+                    // Save the email address in a `ClipData`.
+                    ClipData srcEmailTypeClipData = ClipData.newPlainText(getString(R.string.email_address), linkUrl);
 
-                // Reload the WebView.
-                mainWebView.reload();
-                return true;
+                    // Set the `ClipData` as the clipboard's primary clip.
+                    clipboardManager.setPrimaryClip(srcEmailTypeClipData);
+                    return false;
+                });
 
-            case R.id.toggle_third_party_cookies:
-                if (Build.VERSION.SDK_INT >= 21) {
-                    // Switch the status of thirdPartyCookiesEnabled.
-                    thirdPartyCookiesEnabled = !thirdPartyCookiesEnabled;
+                // Add a `Cancel` entry, which by default closes the `ContextMenu`.
+                menu.add(R.string.cancel);
+                break;
 
-                    // Update the menu checkbox.
-                    menuItem.setChecked(thirdPartyCookiesEnabled);
+            // `IMAGE_TYPE` is an image.
+            case WebView.HitTestResult.IMAGE_TYPE:
+                // Get the image URL.
+                imageUrl = hitTestResult.getExtra();
 
-                    // Apply the new cookie status.
-                    cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
+                // Set the image URL as the title of the `ContextMenu`.
+                menu.setHeaderTitle(imageUrl);
 
-                    // Display a `Snackbar`.
-                    if (thirdPartyCookiesEnabled) {
-                        Snackbar.make(findViewById(R.id.main_webview), R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
-                    } else {
-                        Snackbar.make(findViewById(R.id.main_webview), R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
-                    }
+                // Add a View Image entry.
+                menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
+                    loadUrl(imageUrl);
+                    return false;
+                });
 
-                    // Reload the WebView.
-                    mainWebView.reload();
-                } // Else do nothing because SDK < 21.
-                return true;
+                // Add a Download Image entry.
+                menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
+                    // Check if the download should be processed by an external app.
+                    if (downloadWithExternalApp) {  // Download with an external app.
+                        openUrlWithExternalApp(imageUrl);
+                    } else {  // Download with Android's download manager.
+                        // Check to see if the storage permission has already been granted.
+                        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {  // The storage permission needs to be requested.
+                            // Store the image URL for use by `onRequestPermissionResult()`.
+                            downloadImageUrl = imageUrl;
 
-            case R.id.toggle_dom_storage:
-                // Switch the status of domStorageEnabled.
-                domStorageEnabled = !domStorageEnabled;
+                            // Show a dialog if the user has previously denied the permission.
+                            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
+                                // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
+                                DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
 
-                // Update the menu checkbox.
-                menuItem.setChecked(domStorageEnabled);
+                                // Show the download location permission alert dialog.  The permission will be requested when the dialog is closed.
+                                downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
+                            } else {  // Show the permission request directly.
+                                // Request the permission.  The download dialog will be launched by `onRequestPermissionResult().
+                                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
+                            }
+                        } else {  // The storage permission has already been granted.
+                            // Get a handle for the download image alert dialog.
+                            DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
 
-                // Apply the new DOM Storage status.
-                mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
+                            // Show the download image alert dialog.
+                            downloadImageDialogFragment.show(fragmentManager, getString(R.string.download));
+                        }
+                    }
+                    return false;
+                });
 
-                // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
-                updatePrivacyIcons(true);
+                // Add a Copy URL entry.
+                menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
+                    // Save the image URL in a `ClipData`.
+                    ClipData srcImageTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
 
-                // Display a `Snackbar`.
-                if (domStorageEnabled) {
-                    Snackbar.make(findViewById(R.id.main_webview), R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
-                } else {
-                    Snackbar.make(findViewById(R.id.main_webview), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
-                }
+                    // Set the `ClipData` as the clipboard's primary clip.
+                    clipboardManager.setPrimaryClip(srcImageTypeClipData);
+                    return false;
+                });
+
+                // Add an Open with App entry.
+                menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
+                    openWithApp(imageUrl);
+                    return false;
+                });
 
-                // Reload the WebView.
-                mainWebView.reload();
-                return true;
+                // Add an Open with Browser entry.
+                menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
+                    openWithBrowser(imageUrl);
+                    return false;
+                });
 
-            // Form data can be removed once the minimum API >= 26.
-            case R.id.toggle_save_form_data:
-                // Switch the status of saveFormDataEnabled.
-                saveFormDataEnabled = !saveFormDataEnabled;
+                // Add a `Cancel` entry, which by default closes the `ContextMenu`.
+                menu.add(R.string.cancel);
+                break;
 
-                // Update the menu checkbox.
-                menuItem.setChecked(saveFormDataEnabled);
 
-                // Apply the new form data status.
-                mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
+            // `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link.
+            case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
+                // Get the image URL.
+                imageUrl = hitTestResult.getExtra();
 
-                // Display a `Snackbar`.
-                if (saveFormDataEnabled) {
-                    Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
-                } else {
-                    Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
-                }
+                // Set the image URL as the title of the `ContextMenu`.
+                menu.setHeaderTitle(imageUrl);
 
-                // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
-                updatePrivacyIcons(true);
+                // Add a `View Image` entry.
+                menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
+                    loadUrl(imageUrl);
+                    return false;
+                });
 
-                // Reload the WebView.
-                mainWebView.reload();
-                return true;
+                // Add a `Download Image` entry.
+                menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
+                    // Check if the download should be processed by an external app.
+                    if (downloadWithExternalApp) {  // Download with an external app.
+                        openUrlWithExternalApp(imageUrl);
+                    } else {  // Download with Android's download manager.
+                        // Check to see if the storage permission has already been granted.
+                        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {  // The storage permission needs to be requested.
+                            // Store the image URL for use by `onRequestPermissionResult()`.
+                            downloadImageUrl = imageUrl;
 
-            case R.id.clear_cookies:
-                Snackbar.make(findViewById(R.id.main_webview), R.string.cookies_deleted, Snackbar.LENGTH_LONG)
-                        .setAction(R.string.undo, v -> {
-                            // Do nothing because everything will be handled by `onDismissed()` below.
-                        })
-                        .addCallback(new Snackbar.Callback() {
-                            @SuppressLint("SwitchIntDef")  // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
-                            @Override
-                            public void onDismissed(Snackbar snackbar, int event) {
-                                switch (event) {
-                                    // The user pushed the undo button.
-                                    case Snackbar.Callback.DISMISS_EVENT_ACTION:
-                                        // Do nothing.
-                                        break;
+                            // Show a dialog if the user has previously denied the permission.
+                            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
+                                // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
+                                DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
 
-                                    // The snackbar was dismissed without the undo button being pushed.
-                                    default:
-                                        // `cookieManager.removeAllCookie()` varies by SDK.
-                                        if (Build.VERSION.SDK_INT < 21) {
-                                            cookieManager.removeAllCookie();
-                                        } else {
-                                            cookieManager.removeAllCookies(null);
-                                        }
-                                }
+                                // Show the download location permission alert dialog.  The permission will be requested when the dialog is closed.
+                                downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
+                            } else {  // Show the permission request directly.
+                                // Request the permission.  The download dialog will be launched by `onRequestPermissionResult().
+                                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
                             }
-                        })
-                        .show();
-                return true;
+                        } else {  // The storage permission has already been granted.
+                            // Get a handle for the download image alert dialog.
+                            DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
 
-            case R.id.clear_dom_storage:
-                Snackbar.make(findViewById(R.id.main_webview), R.string.dom_storage_deleted, Snackbar.LENGTH_LONG)
-                        .setAction(R.string.undo, v -> {
-                            // Do nothing because everything will be handled by `onDismissed()` below.
-                        })
-                        .addCallback(new Snackbar.Callback() {
-                            @SuppressLint("SwitchIntDef")  // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
-                            @Override
-                            public void onDismissed(Snackbar snackbar, int event) {
-                                switch (event) {
-                                    // The user pushed the undo button.
-                                    case Snackbar.Callback.DISMISS_EVENT_ACTION:
-                                        // Do nothing.
-                                        break;
+                            // Show the download image alert dialog.
+                            downloadImageDialogFragment.show(fragmentManager, getString(R.string.download));
+                        }
+                    }
+                    return false;
+                });
 
-                                    // The snackbar was dismissed without the undo button being pushed.
-                                    default:
-                                        // Delete the DOM Storage.
-                                        WebStorage webStorage = WebStorage.getInstance();
-                                        webStorage.deleteAllData();
+                // Add a `Copy URL` entry.
+                menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
+                    // Save the image URL in a `ClipData`.
+                    ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
 
-                                        // Initialize a handler to manually delete the DOM storage files and directories.
-                                        Handler deleteDomStorageHandler = new Handler();
+                    // Set the `ClipData` as the clipboard's primary clip.
+                    clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData);
+                    return false;
+                });
 
-                                        // Setup a runnable to manually delete the DOM storage files and directories.
-                                        Runnable deleteDomStorageRunnable = () -> {
-                                            try {
-                                                // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
-                                                Process deleteLocalStorageProcess = privacyBrowserRuntime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
+                // Add an Open with App entry.
+                menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
+                    openWithApp(imageUrl);
+                    return false;
+                });
 
-                                                // Multiple commands must be used because `Runtime.exec()` does not like `*`.
-                                                Process deleteIndexProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
-                                                Process deleteQuotaManagerProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
-                                                Process deleteQuotaManagerJournalProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
-                                                Process deleteDatabasesProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
+                // Add an Open with Browser entry.
+                menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
+                    openWithBrowser(imageUrl);
+                    return false;
+                });
 
-                                                // Wait for the processes to finish.
-                                                deleteLocalStorageProcess.waitFor();
-                                                deleteIndexProcess.waitFor();
-                                                deleteQuotaManagerProcess.waitFor();
-                                                deleteQuotaManagerJournalProcess.waitFor();
-                                                deleteDatabasesProcess.waitFor();
-                                            } catch (Exception exception) {
-                                                // Do nothing if an error is thrown.
-                                            }
-                                        };
+                // Add a `Cancel` entry, which by default closes the `ContextMenu`.
+                menu.add(R.string.cancel);
+                break;
+        }
+    }
 
-                                        // Manually delete the DOM storage files after 200 milliseconds.
-                                        deleteDomStorageHandler.postDelayed(deleteDomStorageRunnable, 200);
-                                }
-                            }
-                        })
-                        .show();
-                return true;
+    @Override
+    public void onCreateBookmark(DialogFragment dialogFragment) {
+        // Get the views from the dialog fragment.
+        EditText createBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_name_edittext);
+        EditText createBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_url_edittext);
 
-            // Form data can be remove once the minimum API >= 26.
-            case R.id.clear_form_data:
-                Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_deleted, Snackbar.LENGTH_LONG)
-                        .setAction(R.string.undo, v -> {
-                            // Do nothing because everything will be handled by `onDismissed()` below.
-                        })
-                        .addCallback(new Snackbar.Callback() {
-                            @SuppressLint("SwitchIntDef")  // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
-                            @Override
-                            public void onDismissed(Snackbar snackbar, int event) {
-                                switch (event) {
-                                    // The user pushed the undo button.
-                                    case Snackbar.Callback.DISMISS_EVENT_ACTION:
-                                        // Do nothing.
-                                        break;
+        // Extract the strings from the edit texts.
+        String bookmarkNameString = createBookmarkNameEditText.getText().toString();
+        String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
 
-                                    // The snackbar was dismissed without the `Undo` button being pushed.
-                                    default:
-                                        // Delete the form data.
-                                        WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(getApplicationContext());
-                                        mainWebViewDatabase.clearFormData();
-                                }
-                            }
-                        })
-                        .show();
-                return true;
+        // Get a copy of the favorite icon bitmap.
+        Bitmap favoriteIcon = favoriteIconBitmap;
 
-            case R.id.easylist:
-                // Toggle the EasyList status.
-                easyListEnabled = !easyListEnabled;
+        // Scale the favorite icon bitmap down if it is larger than 256 x 256.  Filtering uses bilinear interpolation.
+        if ((favoriteIcon.getHeight() > 256) || (favoriteIcon.getWidth() > 256)) {
+            favoriteIcon = Bitmap.createScaledBitmap(favoriteIcon, 256, 256, true);
+        }
 
-                // Update the menu checkbox.
-                menuItem.setChecked(easyListEnabled);
+        // Create a favorite icon byte array output stream.
+        ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
 
-                // Reload the main WebView.
-                mainWebView.reload();
-                return true;
+        // Convert the favorite icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
+        favoriteIcon.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
 
-            case R.id.easyprivacy:
-                // Toggle the EasyPrivacy status.
-                easyPrivacyEnabled = !easyPrivacyEnabled;
+        // Convert the favorite icon byte array stream to a byte array.
+        byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
 
-                // Update the menu checkbox.
-                menuItem.setChecked(easyPrivacyEnabled);
+        // Display the new bookmark below the current items in the (0 indexed) list.
+        int newBookmarkDisplayOrder = bookmarksListView.getCount();
 
-                // Reload the main WebView.
-                mainWebView.reload();
-                return true;
+        // Create the bookmark.
+        bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
 
-            case R.id.fanboys_annoyance_list:
-                // Toggle Fanboy's Annoyance List status.
-                fanboysAnnoyanceListEnabled = !fanboysAnnoyanceListEnabled;
+        // Update the bookmarks cursor with the current contents of this folder.
+        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
 
-                // Update the menu checkbox.
-                menuItem.setChecked(fanboysAnnoyanceListEnabled);
+        // Update the list view.
+        bookmarksCursorAdapter.changeCursor(bookmarksCursor);
 
-                // Update the staus of Fanboy's Social Blocking List.
-                MenuItem fanboysSocialBlockingListMenuItem = mainMenu.findItem(R.id.fanboys_social_blocking_list);
-                fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListEnabled);
+        // Scroll to the new bookmark.
+        bookmarksListView.setSelection(newBookmarkDisplayOrder);
+    }
 
-                // Reload the main WebView.
-                mainWebView.reload();
-                return true;
+    @Override
+    public void onCreateBookmarkFolder(DialogFragment dialogFragment) {
+        // Get handles for the views in the dialog fragment.
+        EditText createFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.create_folder_name_edittext);
+        RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon_radiobutton);
+        ImageView folderIconImageView = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon);
 
-            case R.id.fanboys_social_blocking_list:
-                // Toggle Fanboy's Social Blocking List status.
-                fanboysSocialBlockingListEnabled = !fanboysSocialBlockingListEnabled;
+        // Get new folder name string.
+        String folderNameString = createFolderNameEditText.getText().toString();
 
-                // Update the menu checkbox.
-                menuItem.setChecked(fanboysSocialBlockingListEnabled);
+        // Create a folder icon bitmap.
+        Bitmap folderIconBitmap;
 
-                // Reload the main WebView.
-                mainWebView.reload();
-                return true;
+        // Set the folder icon bitmap according to the dialog.
+        if (defaultFolderIconRadioButton.isChecked()) {  // Use the default folder icon.
+            // Get the default folder icon drawable.
+            Drawable folderIconDrawable = folderIconImageView.getDrawable();
 
-            case R.id.ultraprivacy:
-                // Toggle the UltraPrivacy status.
-                ultraPrivacyEnabled = !ultraPrivacyEnabled;
+            // Convert the folder icon drawable to a bitmap drawable.
+            BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
 
-                // Update the menu checkbox.
-                menuItem.setChecked(ultraPrivacyEnabled);
+            // Convert the folder icon bitmap drawable to a bitmap.
+            folderIconBitmap = folderIconBitmapDrawable.getBitmap();
+        } else {  // Use the WebView favorite icon.
+            // Get a copy of the favorite icon bitmap.
+            folderIconBitmap = favoriteIconBitmap;
 
-                // Reload the main WebView.
-                mainWebView.reload();
-                return true;
+            // Scale the folder icon bitmap down if it is larger than 256 x 256.  Filtering uses bilinear interpolation.
+            if ((folderIconBitmap.getHeight() > 256) || (folderIconBitmap.getWidth() > 256)) {
+                folderIconBitmap = Bitmap.createScaledBitmap(folderIconBitmap, 256, 256, true);
+            }
+        }
 
-            case R.id.block_all_third_party_requests:
-                //Toggle the third-party requests blocker status.
-                blockAllThirdPartyRequests = !blockAllThirdPartyRequests;
+        // Create a folder icon byte array output stream.
+        ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
 
-                // Update the menu checkbox.
-                menuItem.setChecked(blockAllThirdPartyRequests);
+        // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
+        folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
 
-                // Reload the main WebView.
-                mainWebView.reload();
-                return true;
+        // Convert the folder icon byte array stream to a byte array.
+        byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
 
-            case R.id.user_agent_privacy_browser:
-                // Update the user agent.
-                mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[0]);
+        // Move all the bookmarks down one in the display order.
+        for (int i = 0; i < bookmarksListView.getCount(); i++) {
+            int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
+            bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1);
+        }
 
-                // Reload the WebView.
-                mainWebView.reload();
-                return true;
+        // Create the folder, which will be placed at the top of the `ListView`.
+        bookmarksDatabaseHelper.createFolder(folderNameString, currentBookmarksFolder, folderIconByteArray);
 
-            case R.id.user_agent_webview_default:
-                // Update the user agent.
-                mainWebView.getSettings().setUserAgentString("");
+        // Update the bookmarks cursor with the current contents of this folder.
+        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
 
-                // Reload the WebView.
-                mainWebView.reload();
-                return true;
+        // Update the `ListView`.
+        bookmarksCursorAdapter.changeCursor(bookmarksCursor);
 
-            case R.id.user_agent_firefox_on_android:
-                // Update the user agent.
-                mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[2]);
+        // Scroll to the new folder.
+        bookmarksListView.setSelection(0);
+    }
 
-                // Reload the WebView.
-                mainWebView.reload();
-                return true;
+    @Override
+    public void onSaveBookmark(DialogFragment dialogFragment, int selectedBookmarkDatabaseId) {
+        // Get handles for the views from `dialogFragment`.
+        EditText editBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_name_edittext);
+        EditText editBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_url_edittext);
+        RadioButton currentBookmarkIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_current_icon_radiobutton);
 
-            case R.id.user_agent_chrome_on_android:
-                // Update the user agent.
-                mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[3]);
+        // Store the bookmark strings.
+        String bookmarkNameString = editBookmarkNameEditText.getText().toString();
+        String bookmarkUrlString = editBookmarkUrlEditText.getText().toString();
 
-                // Reload the WebView.
-                mainWebView.reload();
-                return true;
+        // Update the bookmark.
+        if (currentBookmarkIconRadioButton.isChecked()) {  // Update the bookmark without changing the favorite icon.
+            bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString);
+        } else {  // Update the bookmark using the `WebView` favorite icon.
+            // Get a copy of the favorite icon bitmap.
+            Bitmap favoriteIcon = favoriteIconBitmap;
 
-            case R.id.user_agent_safari_on_ios:
-                // Update the user agent.
-                mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[4]);
+            // Scale the favorite icon bitmap down if it is larger than 256 x 256.  Filtering uses bilinear interpolation.
+            if ((favoriteIcon.getHeight() > 256) || (favoriteIcon.getWidth() > 256)) {
+                favoriteIcon = Bitmap.createScaledBitmap(favoriteIcon, 256, 256, true);
+            }
 
-                // Reload the WebView.
-                mainWebView.reload();
-                return true;
+            // Create a favorite icon byte array output stream.
+            ByteArrayOutputStream newFavoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
 
-            case R.id.user_agent_firefox_on_linux:
-                // Update the user agent.
-                mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[5]);
+            // Convert the favorite icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
+            favoriteIcon.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream);
 
-                // Reload the WebView.
-                mainWebView.reload();
-                return true;
+            // Convert the favorite icon byte array stream to a byte array.
+            byte[] newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray();
 
-            case R.id.user_agent_chromium_on_linux:
-                // Update the user agent.
-                mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[6]);
+            //  Update the bookmark and the favorite icon.
+            bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray);
+        }
 
-                // Reload the WebView.
-                mainWebView.reload();
-                return true;
+        // Update the bookmarks cursor with the current contents of this folder.
+        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
 
-            case R.id.user_agent_firefox_on_windows:
-                // Update the user agent.
-                mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[7]);
+        // Update the list view.
+        bookmarksCursorAdapter.changeCursor(bookmarksCursor);
+    }
 
-                // Reload the WebView.
-                mainWebView.reload();
-                return true;
+    @Override
+    public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId) {
+        // Get handles for the views from `dialogFragment`.
+        EditText editFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_folder_name_edittext);
+        RadioButton currentFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_current_icon_radiobutton);
+        RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_radiobutton);
+        ImageView defaultFolderIconImageView = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_imageview);
 
-            case R.id.user_agent_chrome_on_windows:
-                // Update the user agent.
-                mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[8]);
+        // Get the new folder name.
+        String newFolderNameString = editFolderNameEditText.getText().toString();
 
-                // Reload the WebView.
-                mainWebView.reload();
-                return true;
+        // Check if the favorite icon has changed.
+        if (currentFolderIconRadioButton.isChecked()) {  // Only the name has changed.
+            // Update the name in the database.
+            bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
+        } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) {  // Only the icon has changed.
+            // Create the new folder icon Bitmap.
+            Bitmap folderIconBitmap;
 
-            case R.id.user_agent_edge_on_windows:
-                // Update the user agent.
-                mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[9]);
+            // Populate the new folder icon bitmap.
+            if (defaultFolderIconRadioButton.isChecked()) {
+                // Get the default folder icon drawable.
+                Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
 
-                // Reload the WebView.
-                mainWebView.reload();
-                return true;
+                // Convert the folder icon drawable to a bitmap drawable.
+                BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
 
-            case R.id.user_agent_internet_explorer_on_windows:
-                // Update the user agent.
-                mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[10]);
+                // Convert the folder icon bitmap drawable to a bitmap.
+                folderIconBitmap = folderIconBitmapDrawable.getBitmap();
+            } else {  // Use the `WebView` favorite icon.
+                // Get a copy of the favorite icon bitmap.
+                folderIconBitmap = MainWebViewActivity.favoriteIconBitmap;
 
-                // Reload the WebView.
-                mainWebView.reload();
-                return true;
+                // Scale the folder icon bitmap down if it is larger than 256 x 256.  Filtering uses bilinear interpolation.
+                if ((folderIconBitmap.getHeight() > 256) || (folderIconBitmap.getWidth() > 256)) {
+                    folderIconBitmap = Bitmap.createScaledBitmap(folderIconBitmap, 256, 256, true);
+                }
+            }
 
-            case R.id.user_agent_safari_on_macos:
-                // Update the user agent.
-                mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[11]);
+            // Create a folder icon byte array output stream.
+            ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
 
-                // Reload the WebView.
-                mainWebView.reload();
-                return true;
+            // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
+            folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
 
-            case R.id.user_agent_custom:
-                // Update the user agent.
-                mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
+            // Convert the folder icon byte array stream to a byte array.
+            byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
 
-                // Reload the WebView.
-                mainWebView.reload();
-                return true;
+            // Update the folder icon in the database.
+            bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderIconByteArray);
+        } else {  // The folder icon and the name have changed.
+            // Get the new folder icon `Bitmap`.
+            Bitmap folderIconBitmap;
+            if (defaultFolderIconRadioButton.isChecked()) {
+                // Get the default folder icon drawable.
+                Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
 
-            case R.id.font_size_twenty_five_percent:
-                mainWebView.getSettings().setTextZoom(25);
-                return true;
+                // Convert the folder icon drawable to a bitmap drawable.
+                BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
 
-            case R.id.font_size_fifty_percent:
-                mainWebView.getSettings().setTextZoom(50);
-                return true;
+                // Convert the folder icon bitmap drawable to a bitmap.
+                folderIconBitmap = folderIconBitmapDrawable.getBitmap();
+            } else {  // Use the `WebView` favorite icon.
+                // Get a copy of the favorite icon bitmap.
+                folderIconBitmap = MainWebViewActivity.favoriteIconBitmap;
 
-            case R.id.font_size_seventy_five_percent:
-                mainWebView.getSettings().setTextZoom(75);
-                return true;
+                // Scale the folder icon bitmap down if it is larger than 256 x 256.  Filtering uses bilinear interpolation.
+                if ((folderIconBitmap.getHeight() > 256) || (folderIconBitmap.getWidth() > 256)) {
+                    folderIconBitmap = Bitmap.createScaledBitmap(folderIconBitmap, 256, 256, true);
+                }
+            }
 
-            case R.id.font_size_one_hundred_percent:
-                mainWebView.getSettings().setTextZoom(100);
-                return true;
+            // Create a folder icon byte array output stream.
+            ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
 
-            case R.id.font_size_one_hundred_twenty_five_percent:
-                mainWebView.getSettings().setTextZoom(125);
-                return true;
+            // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
+            folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
 
-            case R.id.font_size_one_hundred_fifty_percent:
-                mainWebView.getSettings().setTextZoom(150);
-                return true;
+            // Convert the folder icon byte array stream to a byte array.
+            byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
 
-            case R.id.font_size_one_hundred_seventy_five_percent:
-                mainWebView.getSettings().setTextZoom(175);
-                return true;
+            // Update the folder name and icon in the database.
+            bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, newFolderIconByteArray);
+        }
 
-            case R.id.font_size_two_hundred_percent:
-                mainWebView.getSettings().setTextZoom(200);
-                return true;
+        // Update the bookmarks cursor with the current contents of this folder.
+        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
 
-            case R.id.swipe_to_refresh:
-                // Get a handle for the swipe refresh layout.
-                SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
+        // Update the `ListView`.
+        bookmarksCursorAdapter.changeCursor(bookmarksCursor);
+    }
 
-                // Toggle swipe to refresh.
-                swipeRefreshLayout.setEnabled(!swipeRefreshLayout.isEnabled());
-                return true;
+    @Override
+    public void onCloseDownloadLocationPermissionDialog(int downloadType) {
+        switch (downloadType) {
+            case DownloadLocationPermissionDialog.DOWNLOAD_FILE:
+                // Request the WRITE_EXTERNAL_STORAGE permission with a file request code.
+                ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
+                break;
 
-            case R.id.display_images:
-                if (mainWebView.getSettings().getLoadsImagesAutomatically()) {  // Images are currently loaded automatically.
-                    mainWebView.getSettings().setLoadsImagesAutomatically(false);
-                    mainWebView.reload();
-                } else {  // Images are not currently loaded automatically.
-                    mainWebView.getSettings().setLoadsImagesAutomatically(true);
-                }
-                return true;
+            case DownloadLocationPermissionDialog.DOWNLOAD_IMAGE:
+                // Request the WRITE_EXTERNAL_STORAGE permission with an image request code.
+                ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
+                break;
+        }
+    }
 
-            case R.id.night_mode:
-                // Toggle night mode.
-                nightMode = !nightMode;
+    @Override
+    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+        // Get a handle for the fragment manager.
+        FragmentManager fragmentManager = getSupportFragmentManager();
 
-                // Enable or disable JavaScript according to night mode, the global preference, and any domain settings.
-                if (nightMode) {  // Night mode is enabled.  Enable JavaScript.
-                    // Update the global variable.
-                    javaScriptEnabled = true;
-                } else if (domainSettingsApplied) {  // Night mode is disabled and domain settings are applied.  Set JavaScript according to the domain settings.
-                    // Get the JavaScript preference that was stored the last time domain settings were loaded.
-                    javaScriptEnabled = domainSettingsJavaScriptEnabled;
-                } else {  // Night mode is disabled and domain settings are not applied.  Set JavaScript according to the global preference.
-                    // Get a handle for the shared preference.
-                    SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+        switch (requestCode) {
+            case DOWNLOAD_FILE_REQUEST_CODE:
+                // Show the download file alert dialog.  When the dialog closes, the correct command will be used based on the permission status.
+                DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(downloadUrl, downloadContentDisposition, downloadContentLength);
 
-                    // Get the JavaScript preference.
-                    javaScriptEnabled = sharedPreferences.getBoolean("javascript", false);
+                // On API 23, displaying the fragment must be delayed or the app will crash.
+                if (Build.VERSION.SDK_INT == 23) {
+                    new Handler().postDelayed(() -> downloadFileDialogFragment.show(fragmentManager, getString(R.string.download)), 500);
+                } else {
+                    downloadFileDialogFragment.show(fragmentManager, getString(R.string.download));
                 }
 
-                // Apply the JavaScript setting to the WebView.
-                mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
+                // Reset the download variables.
+                downloadUrl = "";
+                downloadContentDisposition = "";
+                downloadContentLength = 0;
+                break;
+
+            case DOWNLOAD_IMAGE_REQUEST_CODE:
+                // Show the download image alert dialog.  When the dialog closes, the correct command will be used based on the permission status.
+                DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(downloadImageUrl);
 
-                // Update the privacy icons.
-                updatePrivacyIcons(false);
+                // On API 23, displaying the fragment must be delayed or the app will crash.
+                if (Build.VERSION.SDK_INT == 23) {
+                    new Handler().postDelayed(() -> downloadImageDialogFragment.show(fragmentManager, getString(R.string.download)), 500);
+                } else {
+                    downloadImageDialogFragment.show(fragmentManager, getString(R.string.download));
+                }
 
-                // Reload the website.
-                mainWebView.reload();
-                return true;
+                // Reset the image URL variable.
+                downloadImageUrl = "";
+                break;
+        }
+    }
 
-            case R.id.find_on_page:
-                // Get a handle for the views.
-                Toolbar toolbar = findViewById(R.id.toolbar);
-                LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
+    @Override
+    public void onDownloadImage(DialogFragment dialogFragment, String imageUrl) {
+        // Download the image if it has an HTTP or HTTPS URI.
+        if (imageUrl.startsWith("http")) {
+            // Get a handle for the system `DOWNLOAD_SERVICE`.
+            DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
 
-                // Hide the toolbar.
-                toolbar.setVisibility(View.GONE);
+            // Parse `imageUrl`.
+            DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(imageUrl));
 
-                // Show the find on page linear layout.
-                findOnPageLinearLayout.setVisibility(View.VISIBLE);
+            // Pass cookies to download manager if cookies are enabled.  This is required to download images from websites that require a login.
+            // Code contributed 2017 Hendrik Knackstedt.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+            if (firstPartyCookiesEnabled) {
+                // Get the cookies for `imageUrl`.
+                String cookies = cookieManager.getCookie(imageUrl);
 
-                // Display the keyboard.  The app must wait 200 ms before running the command to work around a bug in Android.
-                // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
-                findOnPageEditText.postDelayed(() -> {
-                    // Set the focus on `findOnPageEditText`.
-                    findOnPageEditText.requestFocus();
+                // Add the cookies to `downloadRequest`.  In the HTTP request header, cookies are named `Cookie`.
+                downloadRequest.addRequestHeader("Cookie", cookies);
+            }
 
-                    // Display the keyboard.  `0` sets no input flags.
-                    inputMethodManager.showSoftInput(findOnPageEditText, 0);
-                }, 200);
-                return true;
+            // Get the file name from the dialog fragment.
+            EditText downloadImageNameEditText = dialogFragment.getDialog().findViewById(R.id.download_image_name);
+            String imageName = downloadImageNameEditText.getText().toString();
 
-            case R.id.view_source:
-                // Launch the View Source activity.
-                Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
-                startActivity(viewSourceIntent);
-                return true;
+            // Specify the download location.
+            if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {  // External write permission granted.
+                // Download to the public download directory.
+                downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, imageName);
+            } else {  // External write permission denied.
+                // Download to the app's external download directory.
+                downloadRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, imageName);
+            }
 
-            case R.id.share_url:
-                // Setup the share string.
-                String shareString = webViewTitle + " – " + urlTextBox.getText().toString();
+            // Allow `MediaScanner` to index the download if it is a media file.
+            downloadRequest.allowScanningByMediaScanner();
 
-                // Create the share intent.
-                Intent shareIntent = new Intent(Intent.ACTION_SEND);
-                shareIntent.putExtra(Intent.EXTRA_TEXT, shareString);
-                shareIntent.setType("text/plain");
+            // Add the URL as the description for the download.
+            downloadRequest.setDescription(imageUrl);
 
-                // Make it so.
-                startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url)));
-                return true;
+            // Show the download notification after the download is completed.
+            downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
 
-            case R.id.print:
-                // Get a `PrintManager` instance.
-                PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
+            // Remove the lint warning below that `downloadManager` might be `null`.
+            assert downloadManager != null;
 
-                // Convert `mainWebView` to `printDocumentAdapter`.
-                PrintDocumentAdapter printDocumentAdapter = mainWebView.createPrintDocumentAdapter();
+            // Initiate the download.
+            downloadManager.enqueue(downloadRequest);
+        } else {  // The image is not an HTTP or HTTPS URI.
+            Snackbar.make(currentWebView, R.string.cannot_download_image, Snackbar.LENGTH_INDEFINITE).show();
+        }
+    }
 
-                // Remove the lint error below that `printManager` might be `null`.
-                assert printManager != null;
+    @Override
+    public void onDownloadFile(DialogFragment dialogFragment, String downloadUrl) {
+        // Download the file if it has an HTTP or HTTPS URI.
+        if (downloadUrl.startsWith("http")) {
+            // Get a handle for the system `DOWNLOAD_SERVICE`.
+            DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
 
-                // Print the document.  The print attributes are `null`.
-                printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
-                return true;
+            // Parse `downloadUrl`.
+            DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(downloadUrl));
 
-            case R.id.open_with_app:
-                openWithApp(formattedUrlString);
-                return true;
+            // Pass cookies to download manager if cookies are enabled.  This is required to download files from websites that require a login.
+            // Code contributed 2017 Hendrik Knackstedt.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+            if (firstPartyCookiesEnabled) {
+                // Get the cookies for `downloadUrl`.
+                String cookies = cookieManager.getCookie(downloadUrl);
 
-            case R.id.open_with_browser:
-                openWithBrowser(formattedUrlString);
-                return true;
+                // Add the cookies to `downloadRequest`.  In the HTTP request header, cookies are named `Cookie`.
+                downloadRequest.addRequestHeader("Cookie", cookies);
+            }
 
-            case R.id.add_to_homescreen:
-                // Instantiate the create home screen shortcut dialog.
-                DialogFragment createHomeScreenShortcutDialogFragment = CreateHomeScreenShortcutDialog.createDialog(mainWebView.getTitle(), formattedUrlString, favoriteIconBitmap);
+            // Get the file name from the dialog fragment.
+            EditText downloadFileNameEditText = dialogFragment.getDialog().findViewById(R.id.download_file_name);
+            String fileName = downloadFileNameEditText.getText().toString();
 
-                // Show the create home screen shortcut dialog.
-                createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
-                return true;
+            // Specify the download location.
+            if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {  // External write permission granted.
+                // Download to the public download directory.
+                downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
+            } else {  // External write permission denied.
+                // Download to the app's external download directory.
+                downloadRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, fileName);
+            }
 
-            case R.id.proxy_through_orbot:
-                // Toggle the proxy through Orbot variable.
-                proxyThroughOrbot = !proxyThroughOrbot;
+            // Allow `MediaScanner` to index the download if it is a media file.
+            downloadRequest.allowScanningByMediaScanner();
 
-                // Apply the proxy through Orbot settings.
-                applyProxyThroughOrbot(true);
-                return true;
+            // Add the URL as the description for the download.
+            downloadRequest.setDescription(downloadUrl);
 
-            case R.id.refresh:
-                if (menuItem.getTitle().equals(getString(R.string.refresh))) {  // The refresh button was pushed.
-                    // Reload the WebView.
-                    mainWebView.reload();
-                } else {  // The stop button was pushed.
-                    // Stop the loading of the WebView.
-                    mainWebView.stopLoading();
-                }
-                return true;
+            // Show the download notification after the download is completed.
+            downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
 
-            case R.id.ad_consent:
-                // Display the ad consent dialog.
-                DialogFragment adConsentDialogFragment = new AdConsentDialog();
-                adConsentDialogFragment.show(getSupportFragmentManager(), getString(R.string.ad_consent));
-                return true;
+            // Remove the lint warning below that `downloadManager` might be `null`.
+            assert downloadManager != null;
 
-            default:
-                // Don't consume the event.
-                return super.onOptionsItemSelected(menuItem);
+            // Initiate the download.
+            downloadManager.enqueue(downloadRequest);
+        } else {  // The download is not an HTTP or HTTPS URI.
+            Snackbar.make(currentWebView, R.string.cannot_download_file, Snackbar.LENGTH_INDEFINITE).show();
         }
     }
 
-    // removeAllCookies is deprecated, but it is required for API < 21.
-    @SuppressWarnings("deprecation")
     @Override
-    public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
-        int menuItemId = menuItem.getItemId();
-
-        switch (menuItemId) {
-            case R.id.home:
-                loadUrl(homepage);
-                break;
+    public void onHttpAuthenticationCancel() {
+        // Cancel the `HttpAuthHandler`.
+        httpAuthHandler.cancel();
+    }
 
-            case R.id.back:
-                if (mainWebView.canGoBack()) {
-                    // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
-                    formattedUrlString = "";
+    @Override
+    public void onHttpAuthenticationProceed(DialogFragment dialogFragment) {
+        // Get handles for the `EditTexts`.
+        EditText usernameEditText = dialogFragment.getDialog().findViewById(R.id.http_authentication_username);
+        EditText passwordEditText = dialogFragment.getDialog().findViewById(R.id.http_authentication_password);
 
-                    // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
-                    navigatingHistory = true;
+        // Proceed with the HTTP authentication.
+        httpAuthHandler.proceed(usernameEditText.getText().toString(), passwordEditText.getText().toString());
+    }
 
-                    // Load the previous website in the history.
-                    mainWebView.goBack();
-                }
-                break;
+    @Override
+    public void onSslErrorCancel() {
+        sslErrorHandler.cancel();
+    }
 
-            case R.id.forward:
-                if (mainWebView.canGoForward()) {
-                    // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
-                    formattedUrlString = "";
+    @Override
+    public void onSslErrorProceed() {
+        sslErrorHandler.proceed();
+    }
 
-                    // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
-                    navigatingHistory = true;
+    @Override
+    public void onPinnedMismatchBack() {
+        if (currentWebView.canGoBack()) {  // There is a back page in the history.
+            // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
+            formattedUrlString = "";
 
-                    // Load the next website in the history.
-                    mainWebView.goForward();
-                }
-                break;
+            // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
+            navigatingHistory = true;
 
-            case R.id.history:
-                // Get the `WebBackForwardList`.
-                WebBackForwardList webBackForwardList = mainWebView.copyBackForwardList();
+            // Go back.
+            currentWebView.goBack();
+        } else {  // There are no pages to go back to.
+            // Load a blank page
+            loadUrl("");
+        }
+    }
 
-                // Show the URL history dialog and name this instance `R.string.history`.
-                DialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(this, webBackForwardList);
-                urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history));
-                break;
+    @Override
+    public void onPinnedMismatchProceed() {
+        // Do not check the pinned information for this domain again until the domain changes.
+        ignorePinnedDomainInformation = true;
+    }
 
-            case R.id.requests:
-                // Launch the requests activity.
-                Intent requestsIntent = new Intent(this, RequestsActivity.class);
-                startActivity(requestsIntent);
-                break;
+    @Override
+    public void onUrlHistoryEntrySelected(int moveBackOrForwardSteps) {
+        // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
+        formattedUrlString = "";
 
-            case R.id.downloads:
-                // Launch the system Download Manager.
-                Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
+        // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
+        navigatingHistory = true;
 
-                // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list.
-                downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        // Load the history entry.
+        currentWebView.goBackOrForward(moveBackOrForwardSteps);
+    }
 
-                startActivity(downloadManagerIntent);
-                break;
+    @Override
+    public void onClearHistory() {
+        // Clear the history.
+        currentWebView.clearHistory();
+    }
 
-            case R.id.domains:
-                // Set the flag to reapply the domain settings on restart when returning from Domain Settings.
-                reapplyDomainSettingsOnRestart = true;
-                currentDomainName = "";
+    // Override `onBackPressed` to handle the navigation drawer and `mainWebView`.
+    @Override
+    public void onBackPressed() {
+        // Get a handle for the drawer layout.
+        DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
 
-                // Launch the domains activity.
-                Intent domainsIntent = new Intent(this, DomainsActivity.class);
-                startActivity(domainsIntent);
-                break;
+        if (drawerLayout.isDrawerVisible(GravityCompat.START)) {  // The navigation drawer is open.
+            // Close the navigation drawer.
+            drawerLayout.closeDrawer(GravityCompat.START);
+        } else if (drawerLayout.isDrawerVisible(GravityCompat.END)){  // The bookmarks drawer is open.
+            if (currentBookmarksFolder.isEmpty()) {  // The home folder is displayed.
+                // close the bookmarks drawer.
+                drawerLayout.closeDrawer(GravityCompat.END);
+            } else {  // A subfolder is displayed.
+                // Place the former parent folder in `currentFolder`.
+                currentBookmarksFolder = bookmarksDatabaseHelper.getParentFolderName(currentBookmarksFolder);
 
-            case R.id.settings:
-                // Set the flag to reapply app settings on restart when returning from Settings.
-                reapplyAppSettingsOnRestart = true;
+                // Load the new folder.
+                loadBookmarksFolder();
+            }
 
-                // Set the flag to reapply the domain settings on restart when returning from Settings.
-                reapplyDomainSettingsOnRestart = true;
-                currentDomainName = "";
+        } else if (currentWebView.canGoBack()) {  // There is at least one item in the current WebView history.
+            // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
+            formattedUrlString = "";
 
-                // Launch the settings activity.
-                Intent settingsIntent = new Intent(this, SettingsActivity.class);
-                startActivity(settingsIntent);
-                break;
+            // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
+            navigatingHistory = true;
 
-            case R.id.import_export:
-                // Launch the import/export activity.
-                Intent importExportIntent = new Intent (this, ImportExportActivity.class);
-                startActivity(importExportIntent);
-                break;
+            // Go back.
+            currentWebView.goBack();
+        } else {  // There isn't anything to do in Privacy Browser.
+            // Pass `onBackPressed()` to the system.
+            super.onBackPressed();
+        }
+    }
 
-            case R.id.logcat:
-                // Launch the logcat activity.
-                Intent logcatIntent = new Intent(this, LogcatActivity.class);
-                startActivity(logcatIntent);
-                break;
+    // Process the results of an upload file chooser.  Currently there is only one `startActivityForResult` in this activity, so the request code, used to differentiate them, is ignored.
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        // File uploads only work on API >= 21.
+        if (Build.VERSION.SDK_INT >= 21) {
+            // Pass the file to the WebView.
+            fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data));
+        }
+    }
 
-            case R.id.guide:
-                // Launch `GuideActivity`.
-                Intent guideIntent = new Intent(this, GuideActivity.class);
-                startActivity(guideIntent);
-                break;
+    private void loadUrlFromTextBox() {
+        // Get the text from urlTextBox and convert it to a string.  trim() removes white spaces from the beginning and end of the string.
+        String unformattedUrlString = urlTextBox.getText().toString().trim();
 
-            case R.id.about:
-                // Launch `AboutActivity`.
-                Intent aboutIntent = new Intent(this, AboutActivity.class);
-                startActivity(aboutIntent);
-                break;
+        // Check to see if `unformattedUrlString` is a valid URL.  Otherwise, convert it into a search.
+        if (unformattedUrlString.startsWith("content://")) {
+            // Load the entire content URL.
+            formattedUrlString = unformattedUrlString;
+        } else if (Patterns.WEB_URL.matcher(unformattedUrlString).matches() || unformattedUrlString.startsWith("http://") || unformattedUrlString.startsWith("https://")
+                || unformattedUrlString.startsWith("file://")) {
+            // Add `https://` at the beginning if there is no protocol.  Otherwise the app will segfault.
+            if (!unformattedUrlString.startsWith("http") && !unformattedUrlString.startsWith("file://") && !unformattedUrlString.startsWith("content://")) {
+                unformattedUrlString = "https://" + unformattedUrlString;
+            }
 
-            case R.id.clear_and_exit:
-                // Close the bookmarks cursor and database.
-                bookmarksCursor.close();
-                bookmarksDatabaseHelper.close();
+            // Initialize `unformattedUrl`.
+            URL unformattedUrl = null;
 
-                // Get a handle for the shared preferences.
-                SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+            // Convert `unformattedUrlString` to a `URL`, then to a `URI`, and then back to a `String`, which sanitizes the input and adds in any missing components.
+            try {
+                unformattedUrl = new URL(unformattedUrlString);
+            } catch (MalformedURLException e) {
+                e.printStackTrace();
+            }
 
-                // Get the status of the clear everything preference.
-                boolean clearEverything = sharedPreferences.getBoolean("clear_everything", true);
+            // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if `.get` was called on a `null` value.
+            String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
+            String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
+            String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
+            String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
+            String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
 
-                // Clear cookies.
-                if (clearEverything || sharedPreferences.getBoolean("clear_cookies", true)) {
-                    // The command to remove cookies changed slightly in API 21.
-                    if (Build.VERSION.SDK_INT >= 21) {
-                        cookieManager.removeAllCookies(null);
-                    } else {
-                        cookieManager.removeAllCookie();
-                    }
+            // Build the URI.
+            Uri.Builder formattedUri = new Uri.Builder();
+            formattedUri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment);
 
-                    // Manually delete the cookies database, as `CookieManager` sometimes will not flush its changes to disk before `System.exit(0)` is run.
-                    try {
-                        // Two commands must be used because `Runtime.exec()` does not like `*`.
-                        Process deleteCookiesProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies");
-                        Process deleteCookiesJournalProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies-journal");
+            // Decode `formattedUri` as a `String` in `UTF-8`.
+            try {
+                formattedUrlString = URLDecoder.decode(formattedUri.build().toString(), "UTF-8");
+            } catch (UnsupportedEncodingException exception) {
+                // Load a blank string.
+                formattedUrlString = "";
+            }
+        } else if (unformattedUrlString.isEmpty()){  // Load a blank web site.
+            // Load a blank string.
+            formattedUrlString = "";
+        } else {  // Search for the contents of the URL box.
+            // Create an encoded URL String.
+            String encodedUrlString;
 
-                        // Wait until the processes have finished.
-                        deleteCookiesProcess.waitFor();
-                        deleteCookiesJournalProcess.waitFor();
-                    } catch (Exception exception) {
-                        // Do nothing if an error is thrown.
-                    }
-                }
+            // Sanitize the search input.
+            try {
+                encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
+            } catch (UnsupportedEncodingException exception) {
+                encodedUrlString = "";
+            }
 
-                // Clear DOM storage.
-                if (clearEverything || sharedPreferences.getBoolean("clear_dom_storage", true)) {
-                    // Ask `WebStorage` to clear the DOM storage.
-                    WebStorage webStorage = WebStorage.getInstance();
-                    webStorage.deleteAllData();
+            // Add the base search URL.
+            formattedUrlString = searchURL + encodedUrlString;
+        }
 
-                    // Manually delete the DOM storage files and directories, as `WebStorage` sometimes will not flush its changes to disk before `System.exit(0)` is run.
-                    try {
-                        // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
-                        Process deleteLocalStorageProcess = privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
+        // Clear the focus from the URL text box.  Otherwise, proximate typing in the box will retain the colorized formatting instead of being reset during refocus.
+        urlTextBox.clearFocus();
 
-                        // Multiple commands must be used because `Runtime.exec()` does not like `*`.
-                        Process deleteIndexProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
-                        Process deleteQuotaManagerProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
-                        Process deleteQuotaManagerJournalProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
-                        Process deleteDatabaseProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
+        // Make it so.
+        loadUrl(formattedUrlString);
+    }
 
-                        // Wait until the processes have finished.
-                        deleteLocalStorageProcess.waitFor();
-                        deleteIndexProcess.waitFor();
-                        deleteQuotaManagerProcess.waitFor();
-                        deleteQuotaManagerJournalProcess.waitFor();
-                        deleteDatabaseProcess.waitFor();
-                    } catch (Exception exception) {
-                        // Do nothing if an error is thrown.
-                    }
-                }
+    private void loadUrl(String url) {// Apply any custom domain settings.
+        // Set the URL as the formatted URL string so that checking third-party requests works correctly.
+        formattedUrlString = url;
 
-                // Clear form data if the API < 26.
-                if ((Build.VERSION.SDK_INT < 26) && (clearEverything || sharedPreferences.getBoolean("clear_form_data", true))) {
-                    WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
-                    webViewDatabase.clearFormData();
+        // Apply the domain settings.
+        applyDomainSettings(url, true, false);
 
-                    // Manually delete the form data database, as `WebViewDatabase` sometimes will not flush its changes to disk before `System.exit(0)` is run.
-                    try {
-                        // A string array must be used because the database contains a space and `Runtime.exec` will not otherwise escape the string correctly.
-                        Process deleteWebDataProcess = privacyBrowserRuntime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data"});
-                        Process deleteWebDataJournalProcess = privacyBrowserRuntime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data-journal"});
+        // If loading a website, set `urlIsLoading` to prevent changes in the user agent on websites with redirects from reloading the current website.
+        urlIsLoading = !url.equals("");
 
-                        // Wait until the processes have finished.
-                        deleteWebDataProcess.waitFor();
-                        deleteWebDataJournalProcess.waitFor();
-                    } catch (Exception exception) {
-                        // Do nothing if an error is thrown.
-                    }
-                }
+        // Load the URL.
+        currentWebView.loadUrl(url, customHeaders);
+    }
 
-                // Clear the cache.
-                if (clearEverything || sharedPreferences.getBoolean("clear_cache", true)) {
-                    // `true` includes disk files.
-                    mainWebView.clearCache(true);
+    public void findPreviousOnPage(View view) {
+        // Go to the previous highlighted phrase on the page.  `false` goes backwards instead of forwards.
+        currentWebView.findNext(false);
+    }
 
-                    // Manually delete the cache directories.
-                    try {
-                        // Delete the main cache directory.
-                        Process deleteCacheProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache");
+    public void findNextOnPage(View view) {
+        // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards.
+        currentWebView.findNext(true);
+    }
 
-                        // Delete the secondary `Service Worker` cache directory.
-                        // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
-                        Process deleteServiceWorkerProcess = privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
+    public void closeFindOnPage(View view) {
+        // Get a handle for the views.
+        Toolbar toolbar = findViewById(R.id.toolbar);
+        LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
 
-                        // Wait until the processes have finished.
-                        deleteCacheProcess.waitFor();
-                        deleteServiceWorkerProcess.waitFor();
-                    } catch (Exception exception) {
-                        // Do nothing if an error is thrown.
-                    }
-                }
+        // Delete the contents of `find_on_page_edittext`.
+        findOnPageEditText.setText(null);
 
-                // Clear SSL certificate preferences.
-                mainWebView.clearSslPreferences();
+        // Clear the highlighted phrases.
+        currentWebView.clearMatches();
 
-                // Clear the back/forward history.
-                mainWebView.clearHistory();
+        // Hide the find on page linear layout.
+        findOnPageLinearLayout.setVisibility(View.GONE);
 
-                // Clear `formattedUrlString`.
-                formattedUrlString = null;
+        // Show the toolbar.
+        toolbar.setVisibility(View.VISIBLE);
 
-                // Clear `customHeaders`.
-                customHeaders.clear();
+        // Hide the keyboard.
+        inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
+    }
 
-                // Destroy the internal state of `mainWebView`.
-                mainWebView.destroy();
+    private void applyAppSettings() {
+        // Get a handle for the shared preferences.
+        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
-                // Manually delete the `app_webview` folder, which contains the cookies, DOM storage, form data, and `Service Worker` cache.
-                // See `https://code.google.com/p/android/issues/detail?id=233826&thanks=233826&ts=1486670530`.
-                if (clearEverything) {
-                    try {
-                        // Delete the folder.
-                        Process deleteAppWebviewProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview");
+        // Store the values from the shared preferences in variables.
+        incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false);
+        boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false);
+        proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false);
+        fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false);
+        hideAppBar = sharedPreferences.getBoolean("hide_app_bar", true);
+        downloadWithExternalApp = sharedPreferences.getBoolean("download_with_external_app", false);
 
-                        // Wait until the process has finished.
-                        deleteAppWebviewProcess.waitFor();
-                    } catch (Exception exception) {
-                        // Do nothing if an error is thrown.
-                    }
-                }
+        // Get handles for the views that need to be modified.  `getSupportActionBar()` must be used until the minimum API >= 21.
+        FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
+        ActionBar actionBar = getSupportActionBar();
 
-                // Close Privacy Browser.  `finishAndRemoveTask` also removes Privacy Browser from the recent app list.
-                if (Build.VERSION.SDK_INT >= 21) {
-                    finishAndRemoveTask();
-                } else {
-                    finish();
-                }
+        // Remove the incorrect lint warnings below that the action bar might be null.
+        assert actionBar != null;
 
-                // Remove the terminated program from RAM.  The status code is `0`.
-                System.exit(0);
-                break;
+        // Apply the proxy through Orbot settings.
+        applyProxyThroughOrbot(false);
+
+        // Set Do Not Track status.
+        if (doNotTrackEnabled) {
+            customHeaders.put("DNT", "1");
+        } else {
+            customHeaders.remove("DNT");
         }
 
-        // Get a handle for the drawer layout.
-        DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
+        // Set the app bar scrolling.
+        currentWebView.setNestedScrollingEnabled(sharedPreferences.getBoolean("scroll_app_bar", true));
 
-        // Close the navigation drawer.
-        drawerLayout.closeDrawer(GravityCompat.START);
-        return true;
-    }
+        // Update the full screen browsing mode settings.
+        if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) {  // Privacy Browser is currently in full screen browsing mode.
+            // Update the visibility of the app bar, which might have changed in the settings.
+            if (hideAppBar) {
+                actionBar.hide();
+            } else {
+                actionBar.show();
+            }
 
-    @Override
-    public void onPostCreate(Bundle savedInstanceState) {
-        // Run the default commands.
-        super.onPostCreate(savedInstanceState);
+            // Hide the banner ad in the free flavor.
+            if (BuildConfig.FLAVOR.contentEquals("free")) {
+                AdHelper.hideAd(findViewById(R.id.adview));
+            }
 
-        // Sync the state of the DrawerToggle after the default `onRestoreInstanceState()` has finished.  This creates the navigation drawer icon.
-        actionBarDrawerToggle.syncState();
-    }
+            // Remove the translucent status flag.  This is necessary so the root frame layout can fill the entire screen.
+            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
 
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        // Run the default commands.
-        super.onConfigurationChanged(newConfig);
+            /* Hide the system bars.
+             * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+             * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+             * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+             * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+             */
+            rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+                    View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+        } else {  // Privacy Browser is not in full screen browsing mode.
+            // Reset the full screen tracker, which could be true if Privacy Browser was in full screen mode before entering settings and full screen browsing was disabled.
+            inFullScreenBrowsingMode = false;
 
-        // Get the status bar pixel size.
-        int statusBarResourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
-        int statusBarPixelSize = getResources().getDimensionPixelSize(statusBarResourceId);
+            // Show the app bar.
+            actionBar.show();
 
-        // Get the resource density.
-        float screenDensity = getResources().getDisplayMetrics().density;
+            // Show the banner ad in the free flavor.
+            if (BuildConfig.FLAVOR.contentEquals("free")) {
+                // Initialize the ads.  If this isn't the first run, `loadAd()` will be automatically called instead.
+                AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), fragmentManager, getString(R.string.google_app_id), getString(R.string.ad_unit_id));
+            }
 
-        // Recalculate the drawer header padding.
-        drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
-        drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
-        drawerHeaderPaddingBottom = (int) (8 * screenDensity);
+            // Remove the `SYSTEM_UI` flags from the root frame layout.
+            rootFrameLayout.setSystemUiVisibility(0);
 
-        // Reload the ad for the free flavor if not in full screen mode.
-        if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
-            // Reload the ad.  The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
-            AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
+            // Add the translucent status flag.
+            getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
         }
-
-        // `invalidateOptionsMenu` should recalculate the number of action buttons from the menu to display on the app bar, but it doesn't because of the this bug:
-        // https://code.google.com/p/android/issues/detail?id=20493#c8
-        // ActivityCompat.invalidateOptionsMenu(this);
     }
 
-    @Override
-    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
-        // Store the `HitTestResult`.
-        final WebView.HitTestResult hitTestResult = mainWebView.getHitTestResult();
-
-        // Create strings.
-        final String imageUrl;
-        final String linkUrl;
-
-        // Get a handle for the the clipboard and fragment managers.
-        final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
-        FragmentManager fragmentManager = getSupportFragmentManager();
-
-        // Remove the lint errors below that `clipboardManager` might be `null`.
-        assert clipboardManager != null;
 
-        switch (hitTestResult.getType()) {
-            // `SRC_ANCHOR_TYPE` is a link.
-            case WebView.HitTestResult.SRC_ANCHOR_TYPE:
-                // Get the target URL.
-                linkUrl = hitTestResult.getExtra();
+    // `reloadWebsite` is used if returning from the Domains activity.  Otherwise JavaScript might not function correctly if it is newly enabled.
+    // The deprecated `.getDrawable()` must be used until the minimum API >= 21.
+    @SuppressWarnings("deprecation")
+    private boolean applyDomainSettings(String url, boolean resetFavoriteIcon, boolean reloadWebsite) {
+        // Get a handle for the URL edit text.
+        EditText urlEditText = findViewById(R.id.url_edittext);
 
-                // Set the target URL as the title of the `ContextMenu`.
-                menu.setHeaderTitle(linkUrl);
+        // Get the current user agent.
+        String initialUserAgent = currentWebView.getSettings().getUserAgentString();
 
-                // Add a Load URL entry.
-                menu.add(R.string.load_url).setOnMenuItemClickListener((MenuItem item) -> {
-                    loadUrl(linkUrl);
-                    return false;
-                });
+        // Initialize a variable to track if the user agent changes.
+        boolean userAgentChanged = false;
 
-                // Add a Copy URL entry.
-                menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
-                    // Save the link URL in a `ClipData`.
-                    ClipData srcAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
+        // Parse the URL into a URI.
+        Uri uri = Uri.parse(url);
 
-                    // Set the `ClipData` as the clipboard's primary clip.
-                    clipboardManager.setPrimaryClip(srcAnchorTypeClipData);
-                    return false;
-                });
+        // Extract the domain from `uri`.
+        String hostName = uri.getHost();
 
-                // Add a Download URL entry.
-                menu.add(R.string.download_url).setOnMenuItemClickListener((MenuItem item) -> {
-                    // Check if the download should be processed by an external app.
-                    if (downloadWithExternalApp) {  // Download with an external app.
-                        openUrlWithExternalApp(linkUrl);
-                    } else {  // Download with Android's download manager.
-                        // Check to see if the storage permission has already been granted.
-                        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {  // The storage permission needs to be requested.
-                            // Store the variables for future use by `onRequestPermissionsResult()`.
-                            downloadUrl = linkUrl;
-                            downloadContentDisposition = "none";
-                            downloadContentLength = -1;
+        // Initialize `loadingNewDomainName`.
+        boolean loadingNewDomainName;
 
-                            // Show a dialog if the user has previously denied the permission.
-                            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
-                                // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
-                                DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
+        // If either `hostName` or `currentDomainName` are `null`, run the options for loading a new domain name.
+        // The lint suggestion to simplify the `if` statement is incorrect, because `hostName.equals(currentDomainName)` can produce a `null object reference.`
+        //noinspection SimplifiableIfStatement
+        if ((hostName == null) || (currentDomainName == null)) {
+            loadingNewDomainName = true;
+        } else {  // Determine if `hostName` equals `currentDomainName`.
+            loadingNewDomainName = !hostName.equals(currentDomainName);
+        }
 
-                                // Show the download location permission alert dialog.  The permission will be requested when the the dialog is closed.
-                                downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
-                            } else {  // Show the permission request directly.
-                                // Request the permission.  The download dialog will be launched by `onRequestPermissionResult()`.
-                                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
-                            }
-                        } else {  // The storage permission has already been granted.
-                            // Get a handle for the download file alert dialog.
-                            DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(linkUrl, "none", -1);
+        // Strings don't like to be null.
+        if (hostName == null) {
+            hostName = "";
+        }
 
-                            // Show the download file alert dialog.
-                            downloadFileDialogFragment.show(fragmentManager, getString(R.string.download));
-                        }
-                    }
-                    return false;
-                });
+        // Only apply the domain settings if a new domain is being loaded.  This allows the user to set temporary settings for JavaScript, cookies, DOM storage, etc.
+        if (loadingNewDomainName) {
+            // Set the new `hostname` as the `currentDomainName`.
+            currentDomainName = hostName;
 
-                // Add an Open with App entry.
-                menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
-                    openWithApp(linkUrl);
-                    return false;
-                });
+            // Reset the ignoring of pinned domain information.
+            ignorePinnedDomainInformation = false;
 
-                // Add an Open with Browser entry.
-                menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
-                    openWithBrowser(linkUrl);
-                    return false;
-                });
+            // Reset the favorite icon if specified.
+            if (resetFavoriteIcon) {
+                // Store the favorite icon bitmap.
+                favoriteIconBitmap = favoriteIconDefaultBitmap;
 
-                // Add a Cancel entry, which by default closes the context menu.
-                menu.add(R.string.cancel);
-                break;
+                // Get a handle for the tab layout.
+                TabLayout tabLayout = findViewById(R.id.tablayout);
 
-            case WebView.HitTestResult.EMAIL_TYPE:
-                // Get the target URL.
-                linkUrl = hitTestResult.getExtra();
+                // Get the current tab.
+                TabLayout.Tab currentTab = tabLayout.getTabAt(tabLayout.getSelectedTabPosition());
 
-                // Set the target URL as the title of the `ContextMenu`.
-                menu.setHeaderTitle(linkUrl);
+                // Remove the warning below that the current tab might be null.
+                assert currentTab != null;
 
-                // Add a Write Email entry.
-                menu.add(R.string.write_email).setOnMenuItemClickListener(item -> {
-                    // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
-                    Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
+                // Get the current tab custom view.
+                View currentTabCustomView = currentTab.getCustomView();
 
-                    // Parse the url and set it as the data for the `Intent`.
-                    emailIntent.setData(Uri.parse("mailto:" + linkUrl));
+                // Remove the warning below that the current tab custom view might be null.
+                assert currentTabCustomView != null;
 
-                    // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
-                    emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                // Get the current tab favorite icon image view.
+                ImageView currentTabFavoriteIconImageView = currentTabCustomView.findViewById(R.id.favorite_icon_imageview);
 
-                    // Make it so.
-                    startActivity(emailIntent);
-                    return false;
-                });
+                // Set the default favorite icon as the favorite icon for this tab.
+                currentTabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(favoriteIconBitmap, 64, 64, true));
+            }
 
-                // Add a Copy Email Address entry.
-                menu.add(R.string.copy_email_address).setOnMenuItemClickListener(item -> {
-                    // Save the email address in a `ClipData`.
-                    ClipData srcEmailTypeClipData = ClipData.newPlainText(getString(R.string.email_address), linkUrl);
+            // Get a handle for the swipe refresh layout.
+            SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
 
-                    // Set the `ClipData` as the clipboard's primary clip.
-                    clipboardManager.setPrimaryClip(srcEmailTypeClipData);
-                    return false;
-                });
+            // Initialize the database handler.  The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
+            DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
 
-                // Add a `Cancel` entry, which by default closes the `ContextMenu`.
-                menu.add(R.string.cancel);
-                break;
+            // Get a full cursor from `domainsDatabaseHelper`.
+            Cursor domainNameCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomain();
 
-            // `IMAGE_TYPE` is an image.
-            case WebView.HitTestResult.IMAGE_TYPE:
-                // Get the image URL.
-                imageUrl = hitTestResult.getExtra();
+            // Initialize `domainSettingsSet`.
+            Set<String> domainSettingsSet = new HashSet<>();
 
-                // Set the image URL as the title of the `ContextMenu`.
-                menu.setHeaderTitle(imageUrl);
+            // Get the domain name column index.
+            int domainNameColumnIndex = domainNameCursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME);
 
-                // Add a View Image entry.
-                menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
-                    loadUrl(imageUrl);
-                    return false;
-                });
+            // Populate `domainSettingsSet`.
+            for (int i = 0; i < domainNameCursor.getCount(); i++) {
+                // Move `domainsCursor` to the current row.
+                domainNameCursor.moveToPosition(i);
 
-                // Add a Download Image entry.
-                menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
-                    // Check if the download should be processed by an external app.
-                    if (downloadWithExternalApp) {  // Download with an external app.
-                        openUrlWithExternalApp(imageUrl);
-                    } else {  // Download with Android's download manager.
-                        // Check to see if the storage permission has already been granted.
-                        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {  // The storage permission needs to be requested.
-                            // Store the image URL for use by `onRequestPermissionResult()`.
-                            downloadImageUrl = imageUrl;
+                // Store the domain name in `domainSettingsSet`.
+                domainSettingsSet.add(domainNameCursor.getString(domainNameColumnIndex));
+            }
 
-                            // Show a dialog if the user has previously denied the permission.
-                            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
-                                // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
-                                DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
+            // Close `domainNameCursor.
+            domainNameCursor.close();
 
-                                // Show the download location permission alert dialog.  The permission will be requested when the dialog is closed.
-                                downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
-                            } else {  // Show the permission request directly.
-                                // Request the permission.  The download dialog will be launched by `onRequestPermissionResult().
-                                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
-                            }
-                        } else {  // The storage permission has already been granted.
-                            // Get a handle for the download image alert dialog.
-                            DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
+            // Initialize variables to track if domain settings will be applied and, if so, under which name.
+            domainSettingsApplied = false;
+            String domainNameInDatabase = null;
 
-                            // Show the download image alert dialog.
-                            downloadImageDialogFragment.show(fragmentManager, getString(R.string.download));
-                        }
-                    }
-                    return false;
-                });
+            // Check the hostname.
+            if (domainSettingsSet.contains(hostName)) {
+                domainSettingsApplied = true;
+                domainNameInDatabase = hostName;
+            }
 
-                // Add a Copy URL entry.
-                menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
-                    // Save the image URL in a `ClipData`.
-                    ClipData srcImageTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
+            // Check all the subdomains of the host name against wildcard domains in the domain cursor.
+            while (!domainSettingsApplied && hostName.contains(".")) {  // Stop checking if domain settings are already applied or there are no more `.` in the host name.
+                if (domainSettingsSet.contains("*." + hostName)) {  // Check the host name prepended by `*.`.
+                    // Apply the domain settings.
+                    domainSettingsApplied = true;
 
-                    // Set the `ClipData` as the clipboard's primary clip.
-                    clipboardManager.setPrimaryClip(srcImageTypeClipData);
-                    return false;
-                });
+                    // Store the applied domain names as it appears in the database.
+                    domainNameInDatabase = "*." + hostName;
+                }
 
-                // Add an Open with App entry.
-                menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
-                    openWithApp(imageUrl);
-                    return false;
-                });
+                // Strip out the lowest subdomain of of the host name.
+                hostName = hostName.substring(hostName.indexOf(".") + 1);
+            }
 
-                // Add an Open with Browser entry.
-                menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
-                    openWithBrowser(imageUrl);
-                    return false;
-                });
 
-                // Add a `Cancel` entry, which by default closes the `ContextMenu`.
-                menu.add(R.string.cancel);
-                break;
+            // Get a handle for the shared preference.
+            SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
+            // Store the general preference information.
+            String defaultFontSizeString = sharedPreferences.getString("font_size", getString(R.string.font_size_default_value));
+            String defaultUserAgentName = sharedPreferences.getString("user_agent", getString(R.string.user_agent_default_value));
+            defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value));
+            boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true);
+            nightMode = sharedPreferences.getBoolean("night_mode", false);
+            boolean displayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true);
 
-            // `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link.
-            case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
-                // Get the image URL.
-                imageUrl = hitTestResult.getExtra();
+            if (domainSettingsApplied) {  // The url has custom domain settings.
+                // Get a cursor for the current host and move it to the first position.
+                Cursor currentHostDomainSettingsCursor = domainsDatabaseHelper.getCursorForDomainName(domainNameInDatabase);
+                currentHostDomainSettingsCursor.moveToFirst();
 
-                // Set the image URL as the title of the `ContextMenu`.
-                menu.setHeaderTitle(imageUrl);
+                // Get the settings from the cursor.
+                domainSettingsDatabaseId = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper._ID)));
+                javaScriptEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1);
+                firstPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES)) == 1);
+                thirdPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES)) == 1);
+                domStorageEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1);
+                // Form data can be removed once the minimum API >= 26.
+                saveFormDataEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1);
+                easyListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYLIST)) == 1);
+                easyPrivacyEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)) == 1);
+                fanboysAnnoyanceListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1);
+                fanboysSocialBlockingListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)) == 1);
+                ultraPrivacyEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)) == 1);
+                blockAllThirdPartyRequests = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1);
+                String userAgentName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT));
+                int fontSize = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE));
+                int swipeToRefreshInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SWIPE_TO_REFRESH));
+                int nightModeInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.NIGHT_MODE));
+                int displayWebpageImagesInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES));
+                pinnedSslCertificate = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1);
+                pinnedSslIssuedToCName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME));
+                pinnedSslIssuedToOName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION));
+                pinnedSslIssuedToUName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT));
+                pinnedSslIssuedByCName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME));
+                pinnedSslIssuedByOName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION));
+                pinnedSslIssuedByUName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT));
+                pinnedIpAddresses = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)) == 1);
+                pinnedHostIpAddresses = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.IP_ADDRESSES));
 
-                // Add a `View Image` entry.
-                menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
-                    loadUrl(imageUrl);
-                    return false;
-                });
+                // Set `nightMode` according to `nightModeInt`.  If `nightModeInt` is `DomainsDatabaseHelper.NIGHT_MODE_SYSTEM_DEFAULT` the current setting from `sharedPreferences` will be used.
+                switch (nightModeInt) {
+                    case DomainsDatabaseHelper.NIGHT_MODE_ENABLED:
+                        nightMode = true;
+                        break;
 
-                // Add a `Download Image` entry.
-                menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
-                    // Check if the download should be processed by an external app.
-                    if (downloadWithExternalApp) {  // Download with an external app.
-                        openUrlWithExternalApp(imageUrl);
-                    } else {  // Download with Android's download manager.
-                        // Check to see if the storage permission has already been granted.
-                        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {  // The storage permission needs to be requested.
-                            // Store the image URL for use by `onRequestPermissionResult()`.
-                            downloadImageUrl = imageUrl;
+                    case DomainsDatabaseHelper.NIGHT_MODE_DISABLED:
+                        nightMode = false;
+                        break;
+                }
 
-                            // Show a dialog if the user has previously denied the permission.
-                            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
-                                // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
-                                DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
+                // Store the domain JavaScript status.  This is used by the options menu night mode toggle.
+                domainSettingsJavaScriptEnabled = javaScriptEnabled;
 
-                                // Show the download location permission alert dialog.  The permission will be requested when the dialog is closed.
-                                downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
-                            } else {  // Show the permission request directly.
-                                // Request the permission.  The download dialog will be launched by `onRequestPermissionResult().
-                                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
-                            }
-                        } else {  // The storage permission has already been granted.
-                            // Get a handle for the download image alert dialog.
-                            DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
+                // Enable JavaScript if night mode is enabled.
+                if (nightMode) {
+                    javaScriptEnabled = true;
+                }
 
-                            // Show the download image alert dialog.
-                            downloadImageDialogFragment.show(fragmentManager, getString(R.string.download));
-                        }
-                    }
-                    return false;
-                });
+                // Set the pinned SSL certificate start date to `null` if the saved date `long` is 0.
+                if (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)) == 0) {
+                    pinnedSslStartDate = null;
+                } else {
+                    pinnedSslStartDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)));
+                }
 
-                // Add a `Copy URL` entry.
-                menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
-                    // Save the image URL in a `ClipData`.
-                    ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
+                // Set the pinned SSL certificate end date to `null` if the saved date `long` is 0.
+                if (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)) == 0) {
+                    pinnedSslEndDate = null;
+                } else {
+                    pinnedSslEndDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)));
+                }
 
-                    // Set the `ClipData` as the clipboard's primary clip.
-                    clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData);
-                    return false;
-                });
+                // Close `currentHostDomainSettingsCursor`.
+                currentHostDomainSettingsCursor.close();
 
-                // Add an Open with App entry.
-                menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
-                    openWithApp(imageUrl);
-                    return false;
-                });
+                // Apply the domain settings.
+                currentWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
+                cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
+                currentWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
 
-                // Add an Open with Browser entry.
-                menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
-                    openWithBrowser(imageUrl);
-                    return false;
-                });
+                // Apply the form data setting if the API < 26.
+                if (Build.VERSION.SDK_INT < 26) {
+                    currentWebView.getSettings().setSaveFormData(saveFormDataEnabled);
+                }
 
-                // Add a `Cancel` entry, which by default closes the `ContextMenu`.
-                menu.add(R.string.cancel);
-                break;
-        }
-    }
+                // Apply the font size.
+                if (fontSize == 0) {  // Apply the default font size.
+                    currentWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
+                } else {  // Apply the specified font size.
+                    currentWebView.getSettings().setTextZoom(fontSize);
+                }
 
-    @Override
-    public void onCreateBookmark(DialogFragment dialogFragment) {
-        // Get the views from the dialog fragment.
-        EditText createBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_name_edittext);
-        EditText createBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_url_edittext);
+                // Set third-party cookies status if API >= 21.
+                if (Build.VERSION.SDK_INT >= 21) {
+                    cookieManager.setAcceptThirdPartyCookies(currentWebView, thirdPartyCookiesEnabled);
+                }
+
+                // Only set the user agent if the webpage is not currently loading.  Otherwise, changing the user agent on redirects can cause the original website to reload.
+                // <https://redmine.stoutner.com/issues/160>
+                if (!urlIsLoading) {
+                    // Set the user agent.
+                    if (userAgentName.equals(getString(R.string.system_default_user_agent))) {  // Use the system default user agent.
+                        // Get the array position of the default user agent name.
+                        int defaultUserAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
 
-        // Extract the strings from the edit texts.
-        String bookmarkNameString = createBookmarkNameEditText.getText().toString();
-        String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
+                        // Set the user agent according to the system default.
+                        switch (defaultUserAgentArrayPosition) {
+                            case UNRECOGNIZED_USER_AGENT:  // The default user agent name is not on the canonical list.
+                                // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
+                                currentWebView.getSettings().setUserAgentString(defaultUserAgentName);
+                                break;
 
-        // Get a copy of the favorite icon bitmap.
-        Bitmap favoriteIcon = favoriteIconBitmap;
+                            case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
+                                // Set the user agent to `""`, which uses the default value.
+                                currentWebView.getSettings().setUserAgentString("");
+                                break;
 
-        // Scale the favorite icon bitmap down if it is larger than 256 x 256.  Filtering uses bilinear interpolation.
-        if ((favoriteIcon.getHeight() > 256) || (favoriteIcon.getWidth() > 256)) {
-            favoriteIcon = Bitmap.createScaledBitmap(favoriteIcon, 256, 256, true);
-        }
+                            case SETTINGS_CUSTOM_USER_AGENT:
+                                // Set the custom user agent.
+                                currentWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
+                                break;
 
-        // Create a favorite icon byte array output stream.
-        ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
+                            default:
+                                // Get the user agent string from the user agent data array
+                                currentWebView.getSettings().setUserAgentString(userAgentDataArray[defaultUserAgentArrayPosition]);
+                        }
+                    } else {  // Set the user agent according to the stored name.
+                        // Get the array position of the user agent name.
+                        int userAgentArrayPosition = userAgentNamesArray.getPosition(userAgentName);
 
-        // Convert the favorite icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
-        favoriteIcon.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
+                        switch (userAgentArrayPosition) {
+                            case UNRECOGNIZED_USER_AGENT:  // The user agent name contains a custom user agent.
+                                currentWebView.getSettings().setUserAgentString(userAgentName);
+                                break;
 
-        // Convert the favorite icon byte array stream to a byte array.
-        byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
+                            case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
+                                // Set the user agent to `""`, which uses the default value.
+                                currentWebView.getSettings().setUserAgentString("");
+                                break;
 
-        // Display the new bookmark below the current items in the (0 indexed) list.
-        int newBookmarkDisplayOrder = bookmarksListView.getCount();
+                            default:
+                                // Get the user agent string from the user agent data array.
+                                currentWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
+                        }
+                    }
 
-        // Create the bookmark.
-        bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
+                    // Store the applied user agent string, which is used in the View Source activity.
+                    appliedUserAgentString = currentWebView.getSettings().getUserAgentString();
 
-        // Update the bookmarks cursor with the current contents of this folder.
-        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
+                    // Update the user agent change tracker.
+                    userAgentChanged = !appliedUserAgentString.equals(initialUserAgent);
+                }
 
-        // Update the list view.
-        bookmarksCursorAdapter.changeCursor(bookmarksCursor);
+                // Set swipe to refresh.
+                switch (swipeToRefreshInt) {
+                    case DomainsDatabaseHelper.SWIPE_TO_REFRESH_SYSTEM_DEFAULT:
+                        // Set swipe to refresh according to the default.
+                        swipeRefreshLayout.setEnabled(defaultSwipeToRefresh);
+                        break;
 
-        // Scroll to the new bookmark.
-        bookmarksListView.setSelection(newBookmarkDisplayOrder);
-    }
+                    case DomainsDatabaseHelper.SWIPE_TO_REFRESH_ENABLED:
+                        // Enable swipe to refresh.
+                        swipeRefreshLayout.setEnabled(true);
+                        break;
 
-    @Override
-    public void onCreateBookmarkFolder(DialogFragment dialogFragment) {
-        // Get handles for the views in the dialog fragment.
-        EditText createFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.create_folder_name_edittext);
-        RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon_radiobutton);
-        ImageView folderIconImageView = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon);
+                    case DomainsDatabaseHelper.SWIPE_TO_REFRESH_DISABLED:
+                        // Disable swipe to refresh.
+                        swipeRefreshLayout.setEnabled(false);
+                }
 
-        // Get new folder name string.
-        String folderNameString = createFolderNameEditText.getText().toString();
+                // Set the loading of webpage images.
+                switch (displayWebpageImagesInt) {
+                    case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_SYSTEM_DEFAULT:
+                        currentWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
+                        break;
 
-        // Create a folder icon bitmap.
-        Bitmap folderIconBitmap;
+                    case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_ENABLED:
+                        currentWebView.getSettings().setLoadsImagesAutomatically(true);
+                        break;
 
-        // Set the folder icon bitmap according to the dialog.
-        if (defaultFolderIconRadioButton.isChecked()) {  // Use the default folder icon.
-            // Get the default folder icon drawable.
-            Drawable folderIconDrawable = folderIconImageView.getDrawable();
+                    case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_DISABLED:
+                        currentWebView.getSettings().setLoadsImagesAutomatically(false);
+                        break;
+                }
 
-            // Convert the folder icon drawable to a bitmap drawable.
-            BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
+                // Set a green background on `urlTextBox` to indicate that custom domain settings are being used.  We have to use the deprecated `.getDrawable()` until the minimum API >= 21.
+                if (darkTheme) {
+                    urlEditText.setBackground(getResources().getDrawable(R.drawable.url_bar_background_dark_blue));
+                } else {
+                    urlEditText.setBackground(getResources().getDrawable(R.drawable.url_bar_background_light_green));
+                }
+            } else {  // The new URL does not have custom domain settings.  Load the defaults.
+                // Store the values from `sharedPreferences` in variables.
+                javaScriptEnabled = sharedPreferences.getBoolean("javascript", false);
+                firstPartyCookiesEnabled = sharedPreferences.getBoolean("first_party_cookies", false);
+                thirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies", false);
+                domStorageEnabled = sharedPreferences.getBoolean("dom_storage", false);
+                saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data", false);  // Form data can be removed once the minimum API >= 26.
+                easyListEnabled = sharedPreferences.getBoolean("easylist", true);
+                easyPrivacyEnabled = sharedPreferences.getBoolean("easyprivacy", true);
+                fanboysAnnoyanceListEnabled = sharedPreferences.getBoolean("fanboys_annoyance_list", true);
+                fanboysSocialBlockingListEnabled = sharedPreferences.getBoolean("fanboys_social_blocking_list", true);
+                ultraPrivacyEnabled = sharedPreferences.getBoolean("ultraprivacy", true);
+                blockAllThirdPartyRequests = sharedPreferences.getBoolean("block_all_third_party_requests", false);
 
-            // Convert the folder icon bitmap drawable to a bitmap.
-            folderIconBitmap = folderIconBitmapDrawable.getBitmap();
-        } else {  // Use the WebView favorite icon.
-            // Get a copy of the favorite icon bitmap.
-            folderIconBitmap = favoriteIconBitmap;
+                // Set `javaScriptEnabled` to be `true` if `night_mode` is `true`.
+                if (nightMode) {
+                    javaScriptEnabled = true;
+                }
 
-            // Scale the folder icon bitmap down if it is larger than 256 x 256.  Filtering uses bilinear interpolation.
-            if ((folderIconBitmap.getHeight() > 256) || (folderIconBitmap.getWidth() > 256)) {
-                folderIconBitmap = Bitmap.createScaledBitmap(folderIconBitmap, 256, 256, true);
-            }
-        }
+                // Apply the default settings.
+                currentWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
+                cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
+                currentWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
+                currentWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
+                swipeRefreshLayout.setEnabled(defaultSwipeToRefresh);
 
-        // Create a folder icon byte array output stream.
-        ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
+                // Apply the form data setting if the API < 26.
+                if (Build.VERSION.SDK_INT < 26) {
+                    currentWebView.getSettings().setSaveFormData(saveFormDataEnabled);
+                }
 
-        // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
-        folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
+                // Reset the pinned variables.
+                domainSettingsDatabaseId = -1;
+                pinnedSslCertificate = false;
+                pinnedSslIssuedToCName = "";
+                pinnedSslIssuedToOName = "";
+                pinnedSslIssuedToUName = "";
+                pinnedSslIssuedByCName = "";
+                pinnedSslIssuedByOName = "";
+                pinnedSslIssuedByUName = "";
+                pinnedSslStartDate = null;
+                pinnedSslEndDate = null;
+                pinnedIpAddresses = false;
+                pinnedHostIpAddresses = "";
 
-        // Convert the folder icon byte array stream to a byte array.
-        byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
+                // Set third-party cookies status if API >= 21.
+                if (Build.VERSION.SDK_INT >= 21) {
+                    cookieManager.setAcceptThirdPartyCookies(currentWebView, thirdPartyCookiesEnabled);
+                }
 
-        // Move all the bookmarks down one in the display order.
-        for (int i = 0; i < bookmarksListView.getCount(); i++) {
-            int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
-            bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1);
-        }
+                // Only set the user agent if the webpage is not currently loading.  Otherwise, changing the user agent on redirects can cause the original website to reload.
+                // <https://redmine.stoutner.com/issues/160>
+                if (!urlIsLoading) {
+                    // Get the array position of the user agent name.
+                    int userAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
 
-        // Create the folder, which will be placed at the top of the `ListView`.
-        bookmarksDatabaseHelper.createFolder(folderNameString, currentBookmarksFolder, folderIconByteArray);
+                    // Set the user agent.
+                    switch (userAgentArrayPosition) {
+                        case UNRECOGNIZED_USER_AGENT:  // The default user agent name is not on the canonical list.
+                            // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
+                            currentWebView.getSettings().setUserAgentString(defaultUserAgentName);
+                            break;
 
-        // Update the bookmarks cursor with the current contents of this folder.
-        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
+                        case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
+                            // Set the user agent to `""`, which uses the default value.
+                            currentWebView.getSettings().setUserAgentString("");
+                            break;
 
-        // Update the `ListView`.
-        bookmarksCursorAdapter.changeCursor(bookmarksCursor);
+                        case SETTINGS_CUSTOM_USER_AGENT:
+                            // Set the custom user agent.
+                            currentWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
+                            break;
 
-        // Scroll to the new folder.
-        bookmarksListView.setSelection(0);
-    }
+                        default:
+                            // Get the user agent string from the user agent data array
+                            currentWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
+                    }
 
-    @Override
-    public void onSaveBookmark(DialogFragment dialogFragment, int selectedBookmarkDatabaseId) {
-        // Get handles for the views from `dialogFragment`.
-        EditText editBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_name_edittext);
-        EditText editBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_url_edittext);
-        RadioButton currentBookmarkIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_current_icon_radiobutton);
+                    // Store the applied user agent string, which is used in the View Source activity.
+                    appliedUserAgentString = currentWebView.getSettings().getUserAgentString();
 
-        // Store the bookmark strings.
-        String bookmarkNameString = editBookmarkNameEditText.getText().toString();
-        String bookmarkUrlString = editBookmarkUrlEditText.getText().toString();
+                    // Update the user agent change tracker.
+                    userAgentChanged = !appliedUserAgentString.equals(initialUserAgent);
+                }
 
-        // Update the bookmark.
-        if (currentBookmarkIconRadioButton.isChecked()) {  // Update the bookmark without changing the favorite icon.
-            bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString);
-        } else {  // Update the bookmark using the `WebView` favorite icon.
-            // Get a copy of the favorite icon bitmap.
-            Bitmap favoriteIcon = favoriteIconBitmap;
+                // Set the loading of webpage images.
+                currentWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
 
-            // Scale the favorite icon bitmap down if it is larger than 256 x 256.  Filtering uses bilinear interpolation.
-            if ((favoriteIcon.getHeight() > 256) || (favoriteIcon.getWidth() > 256)) {
-                favoriteIcon = Bitmap.createScaledBitmap(favoriteIcon, 256, 256, true);
+                // Set a transparent background on `urlTextBox`.  The deprecated `.getDrawable()` must be used until the minimum API >= 21.
+                urlEditText.setBackgroundDrawable(getResources().getDrawable(R.color.transparent));
             }
 
-            // Create a favorite icon byte array output stream.
-            ByteArrayOutputStream newFavoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
-
-            // Convert the favorite icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
-            favoriteIcon.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream);
-
-            // Convert the favorite icon byte array stream to a byte array.
-            byte[] newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray();
+            // Close the domains database helper.
+            domainsDatabaseHelper.close();
 
-            //  Update the bookmark and the favorite icon.
-            bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray);
+            // Update the privacy icons, but only if `mainMenu` has already been populated.
+            if (mainMenu != null) {
+                updatePrivacyIcons(true);
+            }
         }
 
-        // Update the bookmarks cursor with the current contents of this folder.
-        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
+        // Reload the website if returning from the Domains activity.
+        if (reloadWebsite) {
+            currentWebView.reload();
+        }
 
-        // Update the list view.
-        bookmarksCursorAdapter.changeCursor(bookmarksCursor);
+        // Return the user agent changed status.
+        return userAgentChanged;
     }
 
-    @Override
-    public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId) {
-        // Get handles for the views from `dialogFragment`.
-        EditText editFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_folder_name_edittext);
-        RadioButton currentFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_current_icon_radiobutton);
-        RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_radiobutton);
-        ImageView defaultFolderIconImageView = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_imageview);
+    private void applyProxyThroughOrbot(boolean reloadWebsite) {
+        // Get a handle for the shared preferences.
+        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
-        // Get the new folder name.
-        String newFolderNameString = editFolderNameEditText.getText().toString();
+        // Get the search preferences.
+        String homepageString = sharedPreferences.getString("homepage", getString(R.string.homepage_default_value));
+        String torHomepageString = sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value));
+        String torSearchString = sharedPreferences.getString("tor_search", getString(R.string.tor_search_default_value));
+        String torSearchCustomUrlString = sharedPreferences.getString("tor_search_custom_url", getString(R.string.tor_search_custom_url_default_value));
+        String searchString = sharedPreferences.getString("search", getString(R.string.search_default_value));
+        String searchCustomUrlString = sharedPreferences.getString("search_custom_url", getString(R.string.search_custom_url_default_value));
 
-        // Check if the favorite icon has changed.
-        if (currentFolderIconRadioButton.isChecked()) {  // Only the name has changed.
-            // Update the name in the database.
-            bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
-        } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) {  // Only the icon has changed.
-            // Create the new folder icon Bitmap.
-            Bitmap folderIconBitmap;
+        // Get a handle for the action bar.  `getSupportActionBar()` must be used until the minimum API >= 21.
+        ActionBar actionBar = getSupportActionBar();
 
-            // Populate the new folder icon bitmap.
-            if (defaultFolderIconRadioButton.isChecked()) {
-                // Get the default folder icon drawable.
-                Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
+        // Remove the incorrect lint warning later that the action bar might be null.
+        assert actionBar != null;
 
-                // Convert the folder icon drawable to a bitmap drawable.
-                BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
+        // Set the homepage, search, and proxy options.
+        if (proxyThroughOrbot) {  // Set the Tor options.
+            // Set `torHomepageString` as `homepage`.
+            homepage = torHomepageString;
 
-                // Convert the folder icon bitmap drawable to a bitmap.
-                folderIconBitmap = folderIconBitmapDrawable.getBitmap();
-            } else {  // Use the `WebView` favorite icon.
-                // Get a copy of the favorite icon bitmap.
-                folderIconBitmap = MainWebViewActivity.favoriteIconBitmap;
+            // If formattedUrlString is null assign the homepage to it.
+            if (formattedUrlString == null) {
+                formattedUrlString = homepage;
+            }
 
-                // Scale the folder icon bitmap down if it is larger than 256 x 256.  Filtering uses bilinear interpolation.
-                if ((folderIconBitmap.getHeight() > 256) || (folderIconBitmap.getWidth() > 256)) {
-                    folderIconBitmap = Bitmap.createScaledBitmap(folderIconBitmap, 256, 256, true);
-                }
+            // Set the search URL.
+            if (torSearchString.equals("Custom URL")) {  // Get the custom URL string.
+                searchURL = torSearchCustomUrlString;
+            } else {  // Use the string from the pre-built list.
+                searchURL = torSearchString;
             }
 
-            // Create a folder icon byte array output stream.
-            ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
+            // Set the proxy.  `this` refers to the current activity where an `AlertDialog` might be displayed.
+            OrbotProxyHelper.setProxy(getApplicationContext(), this, "localhost", "8118");
 
-            // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
-            folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
+            // Set the `appBar` background to indicate proxying through Orbot is enabled.  `this` refers to the context.
+            if (darkTheme) {
+                actionBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.dark_blue_30));
+            } else {
+                actionBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.blue_50));
+            }
 
-            // Convert the folder icon byte array stream to a byte array.
-            byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
+            // Check to see if Orbot is ready.
+            if (!orbotStatus.equals("ON")) {  // Orbot is not ready.
+                // Set `waitingForOrbot`.
+                waitingForOrbot = true;
 
-            // Update the folder icon in the database.
-            bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderIconByteArray);
-        } else {  // The folder icon and the name have changed.
-            // Get the new folder icon `Bitmap`.
-            Bitmap folderIconBitmap;
-            if (defaultFolderIconRadioButton.isChecked()) {
-                // Get the default folder icon drawable.
-                Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
+                // Disable the wide view port so that the waiting for Orbot text is displayed correctly.
+                currentWebView.getSettings().setUseWideViewPort(false);
 
-                // Convert the folder icon drawable to a bitmap drawable.
-                BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
+                // Load a waiting page.  `null` specifies no encoding, which defaults to ASCII.
+                currentWebView.loadData(waitingForOrbotHtmlString, "text/html", null);
+            } else if (reloadWebsite) {  // Orbot is ready and the website should be reloaded.
+                // Reload the website.
+                currentWebView.reload();
+            }
+        } else {  // Set the non-Tor options.
+            // Set `homepageString` as `homepage`.
+            homepage = homepageString;
 
-                // Convert the folder icon bitmap drawable to a bitmap.
-                folderIconBitmap = folderIconBitmapDrawable.getBitmap();
-            } else {  // Use the `WebView` favorite icon.
-                // Get a copy of the favorite icon bitmap.
-                folderIconBitmap = MainWebViewActivity.favoriteIconBitmap;
+            // If formattedUrlString is null assign the homepage to it.
+            if (formattedUrlString == null) {
+                formattedUrlString = homepage;
+            }
 
-                // Scale the folder icon bitmap down if it is larger than 256 x 256.  Filtering uses bilinear interpolation.
-                if ((folderIconBitmap.getHeight() > 256) || (folderIconBitmap.getWidth() > 256)) {
-                    folderIconBitmap = Bitmap.createScaledBitmap(folderIconBitmap, 256, 256, true);
-                }
+            // Set the search URL.
+            if (searchString.equals("Custom URL")) {  // Get the custom URL string.
+                searchURL = searchCustomUrlString;
+            } else {  // Use the string from the pre-built list.
+                searchURL = searchString;
             }
 
-            // Create a folder icon byte array output stream.
-            ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
+            // Reset the proxy to default.  The host is `""` and the port is `"0"`.
+            OrbotProxyHelper.setProxy(getApplicationContext(), this, "", "0");
 
-            // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
-            folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
+            // Set the default `appBar` background.  `this` refers to the context.
+            if (darkTheme) {
+                actionBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_900));
+            } else {
+                actionBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_100));
+            }
 
-            // Convert the folder icon byte array stream to a byte array.
-            byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
+            // Reset `waitingForOrbot.
+            waitingForOrbot = false;
 
-            // Update the folder name and icon in the database.
-            bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, newFolderIconByteArray);
+            // Reload the website if requested.
+            if (reloadWebsite) {
+                currentWebView.reload();
+            }
         }
-
-        // Update the bookmarks cursor with the current contents of this folder.
-        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
-
-        // Update the `ListView`.
-        bookmarksCursorAdapter.changeCursor(bookmarksCursor);
     }
 
-    @Override
-    public void onCloseDownloadLocationPermissionDialog(int downloadType) {
-        switch (downloadType) {
-            case DownloadLocationPermissionDialog.DOWNLOAD_FILE:
-                // Request the WRITE_EXTERNAL_STORAGE permission with a file request code.
-                ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
-                break;
+    private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) {
+        // Get handles for the menu items.
+        MenuItem privacyMenuItem = mainMenu.findItem(R.id.toggle_javascript);
+        MenuItem firstPartyCookiesMenuItem = mainMenu.findItem(R.id.toggle_first_party_cookies);
+        MenuItem domStorageMenuItem = mainMenu.findItem(R.id.toggle_dom_storage);
+        MenuItem refreshMenuItem = mainMenu.findItem(R.id.refresh);
 
-            case DownloadLocationPermissionDialog.DOWNLOAD_IMAGE:
-                // Request the WRITE_EXTERNAL_STORAGE permission with an image request code.
-                ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
-                break;
+        // Update the privacy icon.
+        if (javaScriptEnabled) {  // JavaScript is enabled.
+            privacyMenuItem.setIcon(R.drawable.javascript_enabled);
+        } else if (firstPartyCookiesEnabled) {  // JavaScript is disabled but cookies are enabled.
+            privacyMenuItem.setIcon(R.drawable.warning);
+        } else {  // All the dangerous features are disabled.
+            privacyMenuItem.setIcon(R.drawable.privacy_mode);
         }
-    }
-
-    @Override
-    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
-        // Get a handle for the fragment manager.
-        FragmentManager fragmentManager = getSupportFragmentManager();
-
-        switch (requestCode) {
-            case DOWNLOAD_FILE_REQUEST_CODE:
-                // Show the download file alert dialog.  When the dialog closes, the correct command will be used based on the permission status.
-                DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(downloadUrl, downloadContentDisposition, downloadContentLength);
-
-                // On API 23, displaying the fragment must be delayed or the app will crash.
-                if (Build.VERSION.SDK_INT == 23) {
-                    new Handler().postDelayed(() -> downloadFileDialogFragment.show(fragmentManager, getString(R.string.download)), 500);
-                } else {
-                    downloadFileDialogFragment.show(fragmentManager, getString(R.string.download));
-                }
 
-                // Reset the download variables.
-                downloadUrl = "";
-                downloadContentDisposition = "";
-                downloadContentLength = 0;
-                break;
+        // Update the first-party cookies icon.
+        if (firstPartyCookiesEnabled) {  // First-party cookies are enabled.
+            firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_enabled);
+        } else {  // First-party cookies are disabled.
+            if (darkTheme) {
+                firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_dark);
+            } else {
+                firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_light);
+            }
+        }
 
-            case DOWNLOAD_IMAGE_REQUEST_CODE:
-                // Show the download image alert dialog.  When the dialog closes, the correct command will be used based on the permission status.
-                DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(downloadImageUrl);
+        // Update the DOM storage icon.
+        if (javaScriptEnabled && domStorageEnabled) {  // Both JavaScript and DOM storage are enabled.
+            domStorageMenuItem.setIcon(R.drawable.dom_storage_enabled);
+        } else if (javaScriptEnabled) {  // JavaScript is enabled but DOM storage is disabled.
+            if (darkTheme) {
+                domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_dark);
+            } else {
+                domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_light);
+            }
+        } else {  // JavaScript is disabled, so DOM storage is ghosted.
+            if (darkTheme) {
+                domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_dark);
+            } else {
+                domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_light);
+            }
+        }
 
-                // On API 23, displaying the fragment must be delayed or the app will crash.
-                if (Build.VERSION.SDK_INT == 23) {
-                    new Handler().postDelayed(() -> downloadImageDialogFragment.show(fragmentManager, getString(R.string.download)), 500);
-                } else {
-                    downloadImageDialogFragment.show(fragmentManager, getString(R.string.download));
-                }
+        // Update the refresh icon.
+        if (darkTheme) {
+            refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
+        } else {
+            refreshMenuItem.setIcon(R.drawable.refresh_enabled_light);
+        }
 
-                // Reset the image URL variable.
-                downloadImageUrl = "";
-                break;
+        // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`.
+        if (runInvalidateOptionsMenu) {
+            invalidateOptionsMenu();
         }
     }
 
-    @Override
-    public void onDownloadImage(DialogFragment dialogFragment, String imageUrl) {
-        // Download the image if it has an HTTP or HTTPS URI.
-        if (imageUrl.startsWith("http")) {
-            // Get a handle for the system `DOWNLOAD_SERVICE`.
-            DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
+    private void openUrlWithExternalApp(String url) {
+        // Create a download intent.  Not specifying the action type will display the maximum number of options.
+        Intent downloadIntent = new Intent();
 
-            // Parse `imageUrl`.
-            DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(imageUrl));
+        // Set the URI and the MIME type.  Specifying `text/html` displays a good number of options.
+        downloadIntent.setDataAndType(Uri.parse(url), "text/html");
 
-            // Pass cookies to download manager if cookies are enabled.  This is required to download images from websites that require a login.
-            // Code contributed 2017 Hendrik Knackstedt.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
-            if (firstPartyCookiesEnabled) {
-                // Get the cookies for `imageUrl`.
-                String cookies = cookieManager.getCookie(imageUrl);
+        // Flag the intent to open in a new task.
+        downloadIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
-                // Add the cookies to `downloadRequest`.  In the HTTP request header, cookies are named `Cookie`.
-                downloadRequest.addRequestHeader("Cookie", cookies);
-            }
+        // Show the chooser.
+        startActivity(Intent.createChooser(downloadIntent, getString(R.string.open_with)));
+    }
+
+    private void highlightUrlText() {
+        // Only highlight the URL text if the box is not currently selected.
+        if (!urlTextBox.hasFocus()) {
+            // Get the URL string.
+            String urlString = urlTextBox.getText().toString();
+
+            // Highlight the URL according to the protocol.
+            if (urlString.startsWith("file://")) {  // This is a file URL.
+                // De-emphasize only the protocol.
+                urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            } else if (urlString.startsWith("content://")) {
+                // De-emphasize only the protocol.
+                urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 10, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            } else {  // This is a web URL.
+                // Get the index of the `/` immediately after the domain name.
+                int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2));
 
-            // Get the file name from the dialog fragment.
-            EditText downloadImageNameEditText = dialogFragment.getDialog().findViewById(R.id.download_image_name);
-            String imageName = downloadImageNameEditText.getText().toString();
+                // Create a base URL string.
+                String baseUrl;
 
-            // Specify the download location.
-            if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {  // External write permission granted.
-                // Download to the public download directory.
-                downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, imageName);
-            } else {  // External write permission denied.
-                // Download to the app's external download directory.
-                downloadRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, imageName);
-            }
+                // Get the base URL.
+                if (endOfDomainName > 0) {  // There is at least one character after the base URL.
+                    // Get the base URL.
+                    baseUrl = urlString.substring(0, endOfDomainName);
+                } else {  // There are no characters after the base URL.
+                    // Set the base URL to be the entire URL string.
+                    baseUrl = urlString;
+                }
 
-            // Allow `MediaScanner` to index the download if it is a media file.
-            downloadRequest.allowScanningByMediaScanner();
+                // Get the index of the last `.` in the domain.
+                int lastDotIndex = baseUrl.lastIndexOf(".");
 
-            // Add the URL as the description for the download.
-            downloadRequest.setDescription(imageUrl);
+                // Get the index of the penultimate `.` in the domain.
+                int penultimateDotIndex = baseUrl.lastIndexOf(".", lastDotIndex - 1);
 
-            // Show the download notification after the download is completed.
-            downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
+                // Markup the beginning of the URL.
+                if (urlString.startsWith("http://")) {  // Highlight the protocol of connections that are not encrypted.
+                    urlTextBox.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
 
-            // Remove the lint warning below that `downloadManager` might be `null`.
-            assert downloadManager != null;
+                    // De-emphasize subdomains.
+                    if (penultimateDotIndex > 0) {  // There is more than one subdomain in the domain name.
+                        urlTextBox.getText().setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                    }
+                } else if (urlString.startsWith("https://")) {  // De-emphasize the protocol of connections that are encrypted.
+                    if (penultimateDotIndex > 0) {  // There is more than one subdomain in the domain name.
+                        // De-emphasize the protocol and the additional subdomains.
+                        urlTextBox.getText().setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                    } else {  // There is only one subdomain in the domain name.
+                        // De-emphasize only the protocol.
+                        urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                    }
+                }
 
-            // Initiate the download.
-            downloadManager.enqueue(downloadRequest);
-        } else {  // The image is not an HTTP or HTTPS URI.
-            Snackbar.make(mainWebView, R.string.cannot_download_image, Snackbar.LENGTH_INDEFINITE).show();
+                // De-emphasize the text after the domain name.
+                if (endOfDomainName > 0) {
+                    urlTextBox.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                }
+            }
         }
     }
 
-    @Override
-    public void onDownloadFile(DialogFragment dialogFragment, String downloadUrl) {
-        // Download the file if it has an HTTP or HTTPS URI.
-        if (downloadUrl.startsWith("http")) {
-            // Get a handle for the system `DOWNLOAD_SERVICE`.
-            DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
-
-            // Parse `downloadUrl`.
-            DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(downloadUrl));
-
-            // Pass cookies to download manager if cookies are enabled.  This is required to download files from websites that require a login.
-            // Code contributed 2017 Hendrik Knackstedt.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
-            if (firstPartyCookiesEnabled) {
-                // Get the cookies for `downloadUrl`.
-                String cookies = cookieManager.getCookie(downloadUrl);
+    private void loadBookmarksFolder() {
+        // Update the bookmarks cursor with the contents of the bookmarks database for the current folder.
+        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
 
-                // Add the cookies to `downloadRequest`.  In the HTTP request header, cookies are named `Cookie`.
-                downloadRequest.addRequestHeader("Cookie", cookies);
+        // Populate the bookmarks cursor adapter.  `this` specifies the `Context`.  `false` disables `autoRequery`.
+        bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
+            @Override
+            public View newView(Context context, Cursor cursor, ViewGroup parent) {
+                // Inflate the individual item layout.  `false` does not attach it to the root.
+                return getLayoutInflater().inflate(R.layout.bookmarks_drawer_item_linearlayout, parent, false);
             }
 
-            // Get the file name from the dialog fragment.
-            EditText downloadFileNameEditText = dialogFragment.getDialog().findViewById(R.id.download_file_name);
-            String fileName = downloadFileNameEditText.getText().toString();
+            @Override
+            public void bindView(View view, Context context, Cursor cursor) {
+                // Get handles for the views.
+                ImageView bookmarkFavoriteIcon = view.findViewById(R.id.bookmark_favorite_icon);
+                TextView bookmarkNameTextView = view.findViewById(R.id.bookmark_name);
 
-            // Specify the download location.
-            if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {  // External write permission granted.
-                // Download to the public download directory.
-                downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
-            } else {  // External write permission denied.
-                // Download to the app's external download directory.
-                downloadRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, fileName);
-            }
+                // Get the favorite icon byte array from the cursor.
+                byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON));
 
-            // Allow `MediaScanner` to index the download if it is a media file.
-            downloadRequest.allowScanningByMediaScanner();
+                // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
+                Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
 
-            // Add the URL as the description for the download.
-            downloadRequest.setDescription(downloadUrl);
+                // Display the bitmap in `bookmarkFavoriteIcon`.
+                bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap);
 
-            // Show the download notification after the download is completed.
-            downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
+                // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`.
+                String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
+                bookmarkNameTextView.setText(bookmarkNameString);
 
-            // Remove the lint warning below that `downloadManager` might be `null`.
-            assert downloadManager != null;
+                // Make the font bold for folders.
+                if (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {
+                    bookmarkNameTextView.setTypeface(Typeface.DEFAULT_BOLD);
+                } else {  // Reset the font to default for normal bookmarks.
+                    bookmarkNameTextView.setTypeface(Typeface.DEFAULT);
+                }
+            }
+        };
 
-            // Initiate the download.
-            downloadManager.enqueue(downloadRequest);
-        } else {  // The download is not an HTTP or HTTPS URI.
-            Snackbar.make(mainWebView, R.string.cannot_download_file, Snackbar.LENGTH_INDEFINITE).show();
-        }
-    }
+        // Populate the `ListView` with the adapter.
+        bookmarksListView.setAdapter(bookmarksCursorAdapter);
 
-    @Override
-    public void onHttpAuthenticationCancel() {
-        // Cancel the `HttpAuthHandler`.
-        httpAuthHandler.cancel();
+        // Set the bookmarks drawer title.
+        if (currentBookmarksFolder.isEmpty()) {
+            bookmarksTitleTextView.setText(R.string.bookmarks);
+        } else {
+            bookmarksTitleTextView.setText(currentBookmarksFolder);
+        }
     }
 
-    @Override
-    public void onHttpAuthenticationProceed(DialogFragment dialogFragment) {
-        // Get handles for the `EditTexts`.
-        EditText usernameEditText = dialogFragment.getDialog().findViewById(R.id.http_authentication_username);
-        EditText passwordEditText = dialogFragment.getDialog().findViewById(R.id.http_authentication_password);
-
-        // Proceed with the HTTP authentication.
-        httpAuthHandler.proceed(usernameEditText.getText().toString(), passwordEditText.getText().toString());
-    }
+    private void openWithApp(String url) {
+        // Create the open with intent with `ACTION_VIEW`.
+        Intent openWithAppIntent = new Intent(Intent.ACTION_VIEW);
 
-    public void viewSslCertificate(View view) {
-        // Show the `ViewSslCertificateDialog` `AlertDialog` and name this instance `@string/view_ssl_certificate`.
-        DialogFragment viewSslCertificateDialogFragment = new ViewSslCertificateDialog();
-        viewSslCertificateDialogFragment.show(getSupportFragmentManager(), getString(R.string.view_ssl_certificate));
-    }
+        // Set the URI but not the MIME type.  This should open all available apps.
+        openWithAppIntent.setData(Uri.parse(url));
 
-    @Override
-    public void onSslErrorCancel() {
-        sslErrorHandler.cancel();
-    }
+        // Flag the intent to open in a new task.
+        openWithAppIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
-    @Override
-    public void onSslErrorProceed() {
-        sslErrorHandler.proceed();
+        // Show the chooser.
+        startActivity(openWithAppIntent);
     }
 
-    @Override
-    public void onPinnedMismatchBack() {
-        if (mainWebView.canGoBack()) {  // There is a back page in the history.
-            // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
-            formattedUrlString = "";
+    private void openWithBrowser(String url) {
+        // Create the open with intent with `ACTION_VIEW`.
+        Intent openWithBrowserIntent = new Intent(Intent.ACTION_VIEW);
 
-            // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
-            navigatingHistory = true;
+        // Set the URI and the MIME type.  `"text/html"` should load browser options.
+        openWithBrowserIntent.setDataAndType(Uri.parse(url), "text/html");
 
-            // Go back.
-            mainWebView.goBack();
-        } else {  // There are no pages to go back to.
-            // Load a blank page
-            loadUrl("");
-        }
-    }
+        // Flag the intent to open in a new task.
+        openWithBrowserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
-    @Override
-    public void onPinnedMismatchProceed() {
-        // Do not check the pinned information for this domain again until the domain changes.
-        ignorePinnedDomainInformation = true;
+        // Show the chooser.
+        startActivity(openWithBrowserIntent);
     }
 
-    @Override
-    public void onUrlHistoryEntrySelected(int moveBackOrForwardSteps) {
-        // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
-        formattedUrlString = "";
+    private static void checkPinnedMismatch() {
+        if ((pinnedSslCertificate || pinnedIpAddresses) && !ignorePinnedDomainInformation) {
+            // Initialize the current SSL certificate variables.
+            String currentWebsiteIssuedToCName = "";
+            String currentWebsiteIssuedToOName = "";
+            String currentWebsiteIssuedToUName = "";
+            String currentWebsiteIssuedByCName = "";
+            String currentWebsiteIssuedByOName = "";
+            String currentWebsiteIssuedByUName = "";
+            Date currentWebsiteSslStartDate = null;
+            Date currentWebsiteSslEndDate = null;
 
-        // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
-        navigatingHistory = true;
 
-        // Load the history entry.
-        mainWebView.goBackOrForward(moveBackOrForwardSteps);
-    }
+            // Extract the individual pieces of information from the current website SSL certificate if it is not null.
+            if (sslCertificate != null) {
+                currentWebsiteIssuedToCName = sslCertificate.getIssuedTo().getCName();
+                currentWebsiteIssuedToOName = sslCertificate.getIssuedTo().getOName();
+                currentWebsiteIssuedToUName = sslCertificate.getIssuedTo().getUName();
+                currentWebsiteIssuedByCName = sslCertificate.getIssuedBy().getCName();
+                currentWebsiteIssuedByOName = sslCertificate.getIssuedBy().getOName();
+                currentWebsiteIssuedByUName = sslCertificate.getIssuedBy().getUName();
+                currentWebsiteSslStartDate = sslCertificate.getValidNotBeforeDate();
+                currentWebsiteSslEndDate = sslCertificate.getValidNotAfterDate();
+            }
 
-    @Override
-    public void onClearHistory() {
-        // Clear the history.
-        mainWebView.clearHistory();
-    }
+            // Initialize string variables to store the SSL certificate dates.  Strings are needed to compare the values below, which doesn't work with `Dates` if they are `null`.
+            String currentWebsiteSslStartDateString = "";
+            String currentWebsiteSslEndDateString = "";
+            String pinnedSslStartDateString = "";
+            String pinnedSslEndDateString = "";
 
-    // Override `onBackPressed` to handle the navigation drawer and `mainWebView`.
-    @Override
-    public void onBackPressed() {
-        // Get a handle for the drawer layout.
-        DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
+            // Convert the `Dates` to `Strings` if they are not `null`.
+            if (currentWebsiteSslStartDate != null) {
+                currentWebsiteSslStartDateString = currentWebsiteSslStartDate.toString();
+            }
 
-        if (drawerLayout.isDrawerVisible(GravityCompat.START)) {  // The navigation drawer is open.
-            // Close the navigation drawer.
-            drawerLayout.closeDrawer(GravityCompat.START);
-        } else if (drawerLayout.isDrawerVisible(GravityCompat.END)){  // The bookmarks drawer is open.
-            if (currentBookmarksFolder.isEmpty()) {  // The home folder is displayed.
-                // close the bookmarks drawer.
-                drawerLayout.closeDrawer(GravityCompat.END);
-            } else {  // A subfolder is displayed.
-                // Place the former parent folder in `currentFolder`.
-                currentBookmarksFolder = bookmarksDatabaseHelper.getParentFolderName(currentBookmarksFolder);
+            if (currentWebsiteSslEndDate != null) {
+                currentWebsiteSslEndDateString = currentWebsiteSslEndDate.toString();
+            }
 
-                // Load the new folder.
-                loadBookmarksFolder();
+            if (pinnedSslStartDate != null) {
+                pinnedSslStartDateString = pinnedSslStartDate.toString();
             }
 
-        } else if (mainWebView.canGoBack()) {  // There is at least one item in the `WebView` history.
-            // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
-            formattedUrlString = "";
+            if (pinnedSslEndDate != null) {
+                pinnedSslEndDateString = pinnedSslEndDate.toString();
+            }
 
-            // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
-            navigatingHistory = true;
+            // Check to see if the pinned information matches the current information.
+            if ((pinnedIpAddresses && !currentHostIpAddresses.equals(pinnedHostIpAddresses)) || (pinnedSslCertificate && (!currentWebsiteIssuedToCName.equals(pinnedSslIssuedToCName) ||
+                    !currentWebsiteIssuedToOName.equals(pinnedSslIssuedToOName) || !currentWebsiteIssuedToUName.equals(pinnedSslIssuedToUName) ||
+                    !currentWebsiteIssuedByCName.equals(pinnedSslIssuedByCName) || !currentWebsiteIssuedByOName.equals(pinnedSslIssuedByOName) ||
+                    !currentWebsiteIssuedByUName.equals(pinnedSslIssuedByUName) || !currentWebsiteSslStartDateString.equals(pinnedSslStartDateString) ||
+                    !currentWebsiteSslEndDateString.equals(pinnedSslEndDateString)))) {
 
-            // Go back.
-            mainWebView.goBack();
-        } else {  // There isn't anything to do in Privacy Browser.
-            // Pass `onBackPressed()` to the system.
-            super.onBackPressed();
-        }
-    }
+                // Get a handle for the pinned mismatch alert dialog.
+                DialogFragment pinnedMismatchDialogFragment = PinnedMismatchDialog.displayDialog(pinnedSslCertificate, pinnedIpAddresses);
 
-    // Process the results of an upload file chooser.  Currently there is only one `startActivityForResult` in this activity, so the request code, used to differentiate them, is ignored.
-    @Override
-    public void onActivityResult(int requestCode, int resultCode, Intent data) {
-        // File uploads only work on API >= 21.
-        if (Build.VERSION.SDK_INT >= 21) {
-            // Pass the file to the WebView.
-            fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data));
+                // Show the pinned mismatch alert dialog.
+                pinnedMismatchDialogFragment.show(fragmentManager, "Pinned Mismatch");
+            }
         }
     }
 
-    private void loadUrlFromTextBox() {
-        // Get the text from urlTextBox and convert it to a string.  trim() removes white spaces from the beginning and end of the string.
-        String unformattedUrlString = urlTextBox.getText().toString().trim();
+    // This must run asynchronously because it involves a network request.  `String` declares the parameters.  `Void` does not declare progress units.  `String` contains the results.
+    private static class GetHostIpAddresses extends AsyncTask<String, Void, String> {
+        // The weak references are used to determine if the activity have disappeared while the AsyncTask is running.
+        private final WeakReference<Activity> activityWeakReference;
 
-        // Check to see if `unformattedUrlString` is a valid URL.  Otherwise, convert it into a search.
-        if (unformattedUrlString.startsWith("content://")) {
-            // Load the entire content URL.
-            formattedUrlString = unformattedUrlString;
-        } else if (Patterns.WEB_URL.matcher(unformattedUrlString).matches() || unformattedUrlString.startsWith("http://") || unformattedUrlString.startsWith("https://")
-                || unformattedUrlString.startsWith("file://")) {
-            // Add `https://` at the beginning if there is no protocol.  Otherwise the app will segfault.
-            if (!unformattedUrlString.startsWith("http") && !unformattedUrlString.startsWith("file://") && !unformattedUrlString.startsWith("content://")) {
-                unformattedUrlString = "https://" + unformattedUrlString;
-            }
+        GetHostIpAddresses(Activity activity) {
+            // Populate the weak references.
+            activityWeakReference = new WeakReference<>(activity);
+        }
 
-            // Initialize `unformattedUrl`.
-            URL unformattedUrl = null;
+        // `onPreExecute()` operates on the UI thread.
+        @Override
+        protected void onPreExecute() {
+            // Get a handle for the activity.
+            Activity activity = activityWeakReference.get();
 
-            // Convert `unformattedUrlString` to a `URL`, then to a `URI`, and then back to a `String`, which sanitizes the input and adds in any missing components.
-            try {
-                unformattedUrl = new URL(unformattedUrlString);
-            } catch (MalformedURLException e) {
-                e.printStackTrace();
+            // Abort if the activity is gone.
+            if ((activity == null) || activity.isFinishing()) {
+                return;
             }
 
-            // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if `.get` was called on a `null` value.
-            String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
-            String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
-            String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
-            String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
-            String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
+            // Set the getting IP addresses tracker.
+            gettingIpAddresses = true;
+        }
 
-            // Build the URI.
-            Uri.Builder formattedUri = new Uri.Builder();
-            formattedUri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment);
 
-            // Decode `formattedUri` as a `String` in `UTF-8`.
-            try {
-                formattedUrlString = URLDecoder.decode(formattedUri.build().toString(), "UTF-8");
-            } catch (UnsupportedEncodingException exception) {
-                // Load a blank string.
-                formattedUrlString = "";
+        @Override
+        protected String doInBackground(String... domainName) {
+            // Get a handle for the activity.
+            Activity activity = activityWeakReference.get();
+
+            // Abort if the activity is gone.
+            if ((activity == null) || activity.isFinishing()) {
+                // Return an empty spannable string builder.
+                return "";
             }
-        } else if (unformattedUrlString.isEmpty()){  // Load a blank web site.
-            // Load a blank string.
-            formattedUrlString = "";
-        } else {  // Search for the contents of the URL box.
-            // Create an encoded URL String.
-            String encodedUrlString;
 
-            // Sanitize the search input.
+            // Initialize an IP address string builder.
+            StringBuilder ipAddresses = new StringBuilder();
+
+            // Get an array with the IP addresses for the host.
             try {
-                encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
-            } catch (UnsupportedEncodingException exception) {
-                encodedUrlString = "";
+                // 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) {
+                    if (ipAddresses.length() == 0) {  // This is the first IP address.
+                        // Add the IP address to the string builder.
+                        ipAddresses.append(inetAddress.getHostAddress());
+                    } else {  // This is not the first IP address.
+                        // 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.
             }
 
-            // Add the base search URL.
-            formattedUrlString = searchURL + encodedUrlString;
+            // Return the string.
+            return ipAddresses.toString();
         }
 
-        // Clear the focus from the URL text box.  Otherwise, proximate typing in the box will retain the colorized formatting instead of being reset during refocus.
-        urlTextBox.clearFocus();
-
-        // Make it so.
-        loadUrl(formattedUrlString);
-    }
+        // `onPostExecute()` operates on the UI thread.
+        @Override
+        protected void onPostExecute(String ipAddresses) {
+            // Get a handle for the activity.
+            Activity activity = activityWeakReference.get();
 
-    private void loadUrl(String url) {// Apply any custom domain settings.
-        // Set the URL as the formatted URL string so that checking third-party requests works correctly.
-        formattedUrlString = url;
+            // Abort if the activity is gone.
+            if ((activity == null) || activity.isFinishing()) {
+                return;
+            }
 
-        // Apply the domain settings.
-        applyDomainSettings(url, true, false);
+            // Store the IP addresses.
+            currentHostIpAddresses = ipAddresses;
 
-        // If loading a website, set `urlIsLoading` to prevent changes in the user agent on websites with redirects from reloading the current website.
-        urlIsLoading = !url.equals("");
+            if (!urlIsLoading) {
+                checkPinnedMismatch();
+            }
 
-        // Load the URL.
-        mainWebView.loadUrl(url, customHeaders);
+            // Reset the getting IP addresses tracker.
+            gettingIpAddresses = false;
+        }
     }
 
-    public void findPreviousOnPage(View view) {
-        // Go to the previous highlighted phrase on the page.  `false` goes backwards instead of forwards.
-        mainWebView.findNext(false);
-    }
+    private class WebViewPagerAdapter extends FragmentPagerAdapter {
+        // The WebView fragments list contains all the WebViews.
+        private LinkedList<WebViewTabFragment> webViewFragmentsList = new LinkedList<>();
 
-    public void findNextOnPage(View view) {
-        // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards.
-        mainWebView.findNext(true);
-    }
+        // Define the constructor.
+        private WebViewPagerAdapter(FragmentManager fragmentManager){
+            // Run the default commands.
+            super(fragmentManager);
+        }
 
-    public void closeFindOnPage(View view) {
-        // Get a handle for the views.
-        Toolbar toolbar = findViewById(R.id.toolbar);
-        LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
+        @Override
+        public int getCount() {
+            // Return the number of pages.
+            return webViewFragmentsList.size();
+        }
 
-        // Delete the contents of `find_on_page_edittext`.
-        findOnPageEditText.setText(null);
+        @Override
+        public int getItemPosition(@NonNull Object object) {
+            //noinspection SuspiciousMethodCalls
+            if (webViewFragmentsList.contains(object)) {
+                // The tab has not been deleted.
+                return POSITION_UNCHANGED;
+            } else {
+                // The tab has been deleted.
+                return POSITION_NONE;
+            }
+        }
 
-        // Clear the highlighted phrases.
-        mainWebView.clearMatches();
+        @Override
+        public Fragment getItem(int pageNumber) {
+            // Get a WebView for a particular page.  Page numbers are 0 indexed.
+            return webViewFragmentsList.get(pageNumber);
+        }
 
-        // Hide the find on page linear layout.
-        findOnPageLinearLayout.setVisibility(View.GONE);
+        private void addPage() {
+            // Add a new page.  The pages and tabs are 0 indexed, so the size of the current list equals the number of the next page.
+            webViewFragmentsList.add(WebViewTabFragment.createTab(webViewFragmentsList.size()));
 
-        // Show the toolbar.
-        toolbar.setVisibility(View.VISIBLE);
+            // Update the view pager.
+            notifyDataSetChanged();
+        }
 
-        // Hide the keyboard.
-        inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
-    }
+        private void deletePage(int pageNumber) {
+            // Get a handle for the tab layout.
+            TabLayout tabLayout = findViewById(R.id.tablayout);
 
-    private void applyAppSettings() {
-        // Get a handle for the shared preferences.
-        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+            // TODO always move to the next tab if possible.
+            // Select a tab that is not being deleted.
+            if (pageNumber == 0) {  // The first tab is being deleted.
+                // Get a handle for the second tab.  The tabs are 0 indexed.
+                TabLayout.Tab secondTab = tabLayout.getTabAt(1);
 
-        // Store the values from the shared preferences in variables.
-        incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false);
-        boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false);
-        proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false);
-        fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false);
-        hideAppBar = sharedPreferences.getBoolean("hide_app_bar", true);
-        downloadWithExternalApp = sharedPreferences.getBoolean("download_with_external_app", false);
+                // Remove the incorrect lint warning below that the second tab might be null.
+                assert secondTab != null;
 
-        // Get handles for the views that need to be modified.  `getSupportActionBar()` must be used until the minimum API >= 21.
-        FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
-        ActionBar actionBar = getSupportActionBar();
+                // Select the second tab.
+                secondTab.select();
+            } else {  // The first tab is not being deleted.
+                // Get a handle for the previous tab.
+                TabLayout.Tab previousTab = tabLayout.getTabAt(pageNumber - 1);
 
-        // Remove the incorrect lint warnings below that the action bar might be null.
-        assert actionBar != null;
+                // Remove the incorrect lint warning below tha the previous tab might be null.
+                assert previousTab != null;
 
-        // Apply the proxy through Orbot settings.
-        applyProxyThroughOrbot(false);
+                // Select the previous tab.
+                previousTab.select();
+            }
 
-        // Set Do Not Track status.
-        if (doNotTrackEnabled) {
-            customHeaders.put("DNT", "1");
-        } else {
-            customHeaders.remove("DNT");
+            // Delete the page.
+            webViewFragmentsList.remove(pageNumber);
+
+            // Delete the tab.
+            tabLayout.removeTabAt(pageNumber);
+
+            // Update the view pager.
+            notifyDataSetChanged();
         }
+    }
 
-        // Set the app bar scrolling.
-        mainWebView.setNestedScrollingEnabled(sharedPreferences.getBoolean("scroll_app_bar", true));
+    public void addTab(View view) {
+        // Add the new WebView page.
+        webViewPagerAdapter.addPage();
 
-        // Update the full screen browsing mode settings.
-        if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) {  // Privacy Browser is currently in full screen browsing mode.
-            // Update the visibility of the app bar, which might have changed in the settings.
-            if (hideAppBar) {
-                actionBar.hide();
-            } else {
-                actionBar.show();
-            }
+        // Get a handle for the tab layout.
+        TabLayout tabLayout = findViewById(R.id.tablayout);
 
-            // Hide the banner ad in the free flavor.
-            if (BuildConfig.FLAVOR.contentEquals("free")) {
-                AdHelper.hideAd(findViewById(R.id.adview));
-            }
+        // Get a handle for the new tab.  The tabs are 0 indexed.
+        TabLayout.Tab newTab = tabLayout.getTabAt(tabLayout.getTabCount() - 1);
 
-            // Remove the translucent status flag.  This is necessary so the root frame layout can fill the entire screen.
-            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+        // Remove the incorrect warning below that the new tab might be null.
+        assert newTab != null;
 
-            /* Hide the system bars.
-             * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
-             * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
-             * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
-             * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
-             */
-            rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
-                    View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
-        } else {  // Privacy Browser is not in full screen browsing mode.
-            // Reset the full screen tracker, which could be true if Privacy Browser was in full screen mode before entering settings and full screen browsing was disabled.
-            inFullScreenBrowsingMode = false;
+        // Move the tab layout to the new tab.
+        newTab.select();
+    }
 
-            // Show the app bar.
-            actionBar.show();
+    @Override
+    public void initializeWebView(NestedScrollWebView nestedScrollWebView, int tabNumber) {
+        // Get handles for the activity views.
+        final FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
+        final DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
+        final RelativeLayout mainContentRelativeLayout = findViewById(R.id.main_content_relativelayout);
+        final ActionBar actionBar = getSupportActionBar();
+        final TabLayout tabLayout = findViewById(R.id.tablayout);
+        final SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
+
+        // Remove the incorrect lint warnings below that the some of the views might be null.
+        assert actionBar != null;
 
-            // Show the banner ad in the free flavor.
-            if (BuildConfig.FLAVOR.contentEquals("free")) {
-                // Initialize the ads.  If this isn't the first run, `loadAd()` will be automatically called instead.
-                AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), fragmentManager, getString(R.string.google_app_id), getString(R.string.ad_unit_id));
-            }
+        // TODO.  Still doesn't work right.
+        // Create the tab if it doesn't already exist.
+        try {
+            TabLayout.Tab tab = tabLayout.getTabAt(tabNumber);
 
-            // Remove the `SYSTEM_UI` flags from the root frame layout.
-            rootFrameLayout.setSystemUiVisibility(0);
+            assert tab != null;
 
-            // Add the translucent status flag.
-            getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+            tab.getCustomView();
+        } catch (Exception exception) {
+            tabLayout.addTab(tabLayout.newTab());
         }
-    }
 
+        // Get the current tab.
+        TabLayout.Tab currentTab = tabLayout.getTabAt(tabNumber);
 
-    // `reloadWebsite` is used if returning from the Domains activity.  Otherwise JavaScript might not function correctly if it is newly enabled.
-    // The deprecated `.getDrawable()` must be used until the minimum API >= 21.
-    @SuppressWarnings("deprecation")
-    private boolean applyDomainSettings(String url, boolean resetFavoriteIcon, boolean reloadWebsite) {
-        // Get the current user agent.
-        String initialUserAgent = mainWebView.getSettings().getUserAgentString();
+        // Remove the lint warning below that the current tab might be null.
+        assert currentTab != null;
 
-        // Initialize a variable to track if the user agent changes.
-        boolean userAgentChanged = false;
+        // Set a custom view on the current tab.
+        currentTab.setCustomView(R.layout.custom_tab_view);
 
-        // Parse the URL into a URI.
-        Uri uri = Uri.parse(url);
+        // Get the custom view from the tab.
+        View currentTabView = currentTab.getCustomView();
 
-        // Extract the domain from `uri`.
-        String hostName = uri.getHost();
+        // Remove the incorrect warning below that the current tab view might be null.
+        assert currentTabView != null;
 
-        // Initialize `loadingNewDomainName`.
-        boolean loadingNewDomainName;
+        // Get the current views from the tab.
+        ImageView tabFavoriteIconImageView = currentTabView.findViewById(R.id.favorite_icon_imageview);
+        TextView tabTitleTextView = currentTabView.findViewById(R.id.title_textview);
 
-        // If either `hostName` or `currentDomainName` are `null`, run the options for loading a new domain name.
-        // The lint suggestion to simplify the `if` statement is incorrect, because `hostName.equals(currentDomainName)` can produce a `null object reference.`
-        //noinspection SimplifiableIfStatement
-        if ((hostName == null) || (currentDomainName == null)) {
-            loadingNewDomainName = true;
-        } else {  // Determine if `hostName` equals `currentDomainName`.
-            loadingNewDomainName = !hostName.equals(currentDomainName);
-        }
 
-        // Strings don't like to be null.
-        if (hostName == null) {
-            hostName = "";
-        }
+        //TODO
+        final ProgressBar progressBar = findViewById(R.id.progress_bar);
 
-        // Only apply the domain settings if a new domain is being loaded.  This allows the user to set temporary settings for JavaScript, cookies, DOM storage, etc.
-        if (loadingNewDomainName) {
-            // Set the new `hostname` as the `currentDomainName`.
-            currentDomainName = hostName;
+        // Get a handle for the shared preferences.
+        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
-            // Reset the ignoring of pinned domain information.
-            ignorePinnedDomainInformation = false;
+        // Get the relevant preferences.
+        boolean downloadWithExternalApp = sharedPreferences.getBoolean("download_with_external_app", false);
 
-            // Reset the favorite icon if specified.
-            if (resetFavoriteIcon) {
-                favoriteIconBitmap = favoriteIconDefaultBitmap;
-                favoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(favoriteIconBitmap, 64, 64, true));
-            }
+        // Allow pinch to zoom.
+        nestedScrollWebView.getSettings().setBuiltInZoomControls(true);
 
-            // Get a handle for the swipe refresh layout.
-            SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
+        // Hide zoom controls.
+        nestedScrollWebView.getSettings().setDisplayZoomControls(false);
 
-            // Initialize the database handler.  The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
-            DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
+        // Don't allow mixed content (HTTP and HTTPS) on the same website.
+        if (Build.VERSION.SDK_INT >= 21) {
+            nestedScrollWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
+        }
 
-            // Get a full cursor from `domainsDatabaseHelper`.
-            Cursor domainNameCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomain();
+        // Set the WebView to use a wide viewport.  Otherwise, some web pages will be scrunched and some content will render outside the screen.
+        nestedScrollWebView.getSettings().setUseWideViewPort(true);
 
-            // Initialize `domainSettingsSet`.
-            Set<String> domainSettingsSet = new HashSet<>();
+        // Set the WebView to load in overview mode (zoomed out to the maximum width).
+        nestedScrollWebView.getSettings().setLoadWithOverviewMode(true);
 
-            // Get the domain name column index.
-            int domainNameColumnIndex = domainNameCursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME);
+        // Explicitly disable geolocation.
+        nestedScrollWebView.getSettings().setGeolocationEnabled(false);
 
-            // Populate `domainSettingsSet`.
-            for (int i = 0; i < domainNameCursor.getCount(); i++) {
-                // Move `domainsCursor` to the current row.
-                domainNameCursor.moveToPosition(i);
+        // Create a double-tap gesture detector to toggle full-screen mode.
+        GestureDetector doubleTapGestureDetector = new GestureDetector(getApplicationContext(), new GestureDetector.SimpleOnGestureListener() {
+            // Override `onDoubleTap()`.  All other events are handled using the default settings.
+            @Override
+            public boolean onDoubleTap(MotionEvent event) {
+                if (fullScreenBrowsingModeEnabled) {  // Only process the double-tap if full screen browsing mode is enabled.
+                    // Toggle the full screen browsing mode tracker.
+                    inFullScreenBrowsingMode = !inFullScreenBrowsingMode;
 
-                // Store the domain name in `domainSettingsSet`.
-                domainSettingsSet.add(domainNameCursor.getString(domainNameColumnIndex));
-            }
+                    // Toggle the full screen browsing mode.
+                    if (inFullScreenBrowsingMode) {  // Switch to full screen mode.
+                        // Hide the app bar if specified.
+                        if (hideAppBar) {
+                            actionBar.hide();
+                        }
 
-            // Close `domainNameCursor.
-            domainNameCursor.close();
+                        // Hide the banner ad in the free flavor.
+                        if (BuildConfig.FLAVOR.contentEquals("free")) {
+                            AdHelper.hideAd(findViewById(R.id.adview));
+                        }
 
-            // Initialize variables to track if domain settings will be applied and, if so, under which name.
-            domainSettingsApplied = false;
-            String domainNameInDatabase = null;
+                        // Remove the translucent status flag.  This is necessary so the root frame layout can fill the entire screen.
+                        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
 
-            // Check the hostname.
-            if (domainSettingsSet.contains(hostName)) {
-                domainSettingsApplied = true;
-                domainNameInDatabase = hostName;
-            }
+                        /* Hide the system bars.
+                         * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+                         * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+                         * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+                         * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+                         */
+                        rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+                                View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+                    } else {  // Switch to normal viewing mode.
+                        // Show the app bar.
+                        actionBar.show();
 
-            // Check all the subdomains of the host name against wildcard domains in the domain cursor.
-            while (!domainSettingsApplied && hostName.contains(".")) {  // Stop checking if domain settings are already applied or there are no more `.` in the host name.
-                if (domainSettingsSet.contains("*." + hostName)) {  // Check the host name prepended by `*.`.
-                    // Apply the domain settings.
-                    domainSettingsApplied = true;
+                        // Show the banner ad in the free flavor.
+                        if (BuildConfig.FLAVOR.contentEquals("free")) {
+                            // Reload the ad.
+                            AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
+                        }
 
-                    // Store the applied domain names as it appears in the database.
-                    domainNameInDatabase = "*." + hostName;
-                }
+                        // Remove the `SYSTEM_UI` flags from the root frame layout.
+                        rootFrameLayout.setSystemUiVisibility(0);
 
-                // Strip out the lowest subdomain of of the host name.
-                hostName = hostName.substring(hostName.indexOf(".") + 1);
+                        // Add the translucent status flag.
+                        getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+                    }
+
+                    // Consume the double-tap.
+                    return true;
+                } else { // Do not consume the double-tap because full screen browsing mode is disabled.
+                    return false;
+                }
             }
+        });
 
+        // Pass all touch events on the WebView through the double-tap gesture detector.
+        nestedScrollWebView.setOnTouchListener((View view, MotionEvent event) -> {
+            // Call `performClick()` on the view, which is required for accessibility.
+            view.performClick();
 
-            // Get a handle for the shared preference.
-            SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+            // Send the event to the gesture detector.
+            return doubleTapGestureDetector.onTouchEvent(event);
+        });
 
-            // Store the general preference information.
-            String defaultFontSizeString = sharedPreferences.getString("font_size", getString(R.string.font_size_default_value));
-            String defaultUserAgentName = sharedPreferences.getString("user_agent", getString(R.string.user_agent_default_value));
-            defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value));
-            boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true);
-            nightMode = sharedPreferences.getBoolean("night_mode", false);
-            boolean displayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true);
+        // Register the WebView for a context menu.  This is used to see link targets and download images.
+        registerForContextMenu(nestedScrollWebView);
 
-            if (domainSettingsApplied) {  // The url has custom domain settings.
-                // Get a cursor for the current host and move it to the first position.
-                Cursor currentHostDomainSettingsCursor = domainsDatabaseHelper.getCursorForDomainName(domainNameInDatabase);
-                currentHostDomainSettingsCursor.moveToFirst();
+        // Allow the downloading of files.
+        nestedScrollWebView.setDownloadListener((String url, String userAgent, String contentDisposition, String mimetype, long contentLength) -> {
+            // Check if the download should be processed by an external app.
+            if (downloadWithExternalApp) {  // Download with an external app.
+                // Create a download intent.  Not specifying the action type will display the maximum number of options.
+                Intent downloadIntent = new Intent();
 
-                // Get the settings from the cursor.
-                domainSettingsDatabaseId = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper._ID)));
-                javaScriptEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1);
-                firstPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES)) == 1);
-                thirdPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES)) == 1);
-                domStorageEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1);
-                // Form data can be removed once the minimum API >= 26.
-                saveFormDataEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1);
-                easyListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYLIST)) == 1);
-                easyPrivacyEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)) == 1);
-                fanboysAnnoyanceListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1);
-                fanboysSocialBlockingListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)) == 1);
-                ultraPrivacyEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)) == 1);
-                blockAllThirdPartyRequests = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1);
-                String userAgentName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT));
-                int fontSize = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE));
-                int swipeToRefreshInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SWIPE_TO_REFRESH));
-                int nightModeInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.NIGHT_MODE));
-                int displayWebpageImagesInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES));
-                pinnedSslCertificate = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1);
-                pinnedSslIssuedToCName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME));
-                pinnedSslIssuedToOName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION));
-                pinnedSslIssuedToUName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT));
-                pinnedSslIssuedByCName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME));
-                pinnedSslIssuedByOName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION));
-                pinnedSslIssuedByUName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT));
-                pinnedIpAddresses = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)) == 1);
-                pinnedHostIpAddresses = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.IP_ADDRESSES));
+                // Set the URI and the MIME type.  Specifying `text/html` displays a good number of options.
+                downloadIntent.setDataAndType(Uri.parse(url), "text/html");
 
-                // Set `nightMode` according to `nightModeInt`.  If `nightModeInt` is `DomainsDatabaseHelper.NIGHT_MODE_SYSTEM_DEFAULT` the current setting from `sharedPreferences` will be used.
-                switch (nightModeInt) {
-                    case DomainsDatabaseHelper.NIGHT_MODE_ENABLED:
-                        nightMode = true;
-                        break;
+                // Flag the intent to open in a new task.
+                downloadIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
-                    case DomainsDatabaseHelper.NIGHT_MODE_DISABLED:
-                        nightMode = false;
-                        break;
-                }
+                // Show the chooser.
+                startActivity(Intent.createChooser(downloadIntent, getString(R.string.open_with)));
+            } else {  // Download with Android's download manager.
+                // Check to see if the WRITE_EXTERNAL_STORAGE permission has already been granted.
+                if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {  // The storage permission has not been granted.
+                    // The WRITE_EXTERNAL_STORAGE permission needs to be requested.
 
-                // Store the domain JavaScript status.  This is used by the options menu night mode toggle.
-                domainSettingsJavaScriptEnabled = javaScriptEnabled;
+                    // Store the variables for future use by `onRequestPermissionsResult()`.
+                    downloadUrl = url;
+                    downloadContentDisposition = contentDisposition;
+                    downloadContentLength = contentLength;
 
-                // Enable JavaScript if night mode is enabled.
-                if (nightMode) {
-                    javaScriptEnabled = true;
+                    // Show a dialog if the user has previously denied the permission.
+                    if (ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
+                        // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
+                        DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
+
+                        // Show the download location permission alert dialog.  The permission will be requested when the the dialog is closed.
+                        downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
+                    } else {  // Show the permission request directly.
+                        // Request the permission.  The download dialog will be launched by `onRequestPermissionResult()`.
+                        ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
+                    }
+                } else {  // The storage permission has already been granted.
+                    // Get a handle for the download file alert dialog.
+                    DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength);
+
+                    // Show the download file alert dialog.
+                    downloadFileDialogFragment.show(fragmentManager, getString(R.string.download));
                 }
+            }
+        });
 
-                // Set the pinned SSL certificate start date to `null` if the saved date `long` is 0.
-                if (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)) == 0) {
-                    pinnedSslStartDate = null;
-                } else {
-                    pinnedSslStartDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)));
+        // Update the find on page count.
+        nestedScrollWebView.setFindListener(new WebView.FindListener() {
+            // Get a handle for `findOnPageCountTextView`.
+            final TextView findOnPageCountTextView = findViewById(R.id.find_on_page_count_textview);
+
+            @Override
+            public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
+                if ((isDoneCounting) && (numberOfMatches == 0)) {  // There are no matches.
+                    // Set `findOnPageCountTextView` to `0/0`.
+                    findOnPageCountTextView.setText(R.string.zero_of_zero);
+                } else if (isDoneCounting) {  // There are matches.
+                    // `activeMatchOrdinal` is zero-based.
+                    int activeMatch = activeMatchOrdinal + 1;
+
+                    // Build the match string.
+                    String matchString = activeMatch + "/" + numberOfMatches;
+
+                    // Set `findOnPageCountTextView`.
+                    findOnPageCountTextView.setText(matchString);
                 }
+            }
+        });
+
+        // Set the web chrome client.
+        nestedScrollWebView.setWebChromeClient(new WebChromeClient() {
+            // Update the progress bar when a page is loading.
+            @Override
+            public void onProgressChanged(WebView view, int progress) {
+                // Inject the night mode CSS if night mode is enabled.
+                if (nightMode) {
+                    // `background-color: #212121` sets the background to be dark gray.  `color: #BDBDBD` sets the text color to be light gray.  `box-shadow: none` removes a lower underline on links
+                    // used by WordPress.  `text-decoration: none` removes all text underlines.  `text-shadow: none` removes text shadows, which usually have a hard coded color.
+                    // `border: none` removes all borders, which can also be used to underline text.  `a {color: #1565C0}` sets links to be a dark blue.
+                    // `::selection {background: #0D47A1}' sets the text selection highlight color to be a dark blue. `!important` takes precedent over any existing sub-settings.
+                    nestedScrollWebView.evaluateJavascript("(function() {var parent = document.getElementsByTagName('head').item(0); var style = document.createElement('style'); style.type = 'text/css'; " +
+                            "style.innerHTML = '* {background-color: #212121 !important; color: #BDBDBD !important; box-shadow: none !important; text-decoration: none !important;" +
+                            "text-shadow: none !important; border: none !important;} a {color: #1565C0 !important;} ::selection {background: #0D47A1 !important;}'; parent.appendChild(style)})()", value -> {
+                        // Initialize a handler to display `mainWebView`.
+                        Handler displayWebViewHandler = new Handler();
+
+                        // Setup a runnable to display `mainWebView` after a delay to allow the CSS to be applied.
+                        Runnable displayWebViewRunnable = () -> {
+                            // Only display `mainWebView` if the progress bar is gone.  This prevents the display of the `WebView` while it is still loading.
+                            if (progressBar.getVisibility() == View.GONE) {
+                                nestedScrollWebView.setVisibility(View.VISIBLE);
+                            }
+                        };
 
-                // Set the pinned SSL certificate end date to `null` if the saved date `long` is 0.
-                if (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)) == 0) {
-                    pinnedSslEndDate = null;
-                } else {
-                    pinnedSslEndDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)));
+                        // Displaying of `mainWebView` after 500 milliseconds.
+                        displayWebViewHandler.postDelayed(displayWebViewRunnable, 500);
+                    });
                 }
 
-                // Close `currentHostDomainSettingsCursor`.
-                currentHostDomainSettingsCursor.close();
+                // Update the progress bar.
+                progressBar.setProgress(progress);
 
-                // Apply the domain settings.
-                mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
-                cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
-                mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
+                // Set the visibility of the progress bar.
+                if (progress < 100) {
+                    // Show the progress bar.
+                    progressBar.setVisibility(View.VISIBLE);
+                } else {
+                    // Hide the progress bar.
+                    progressBar.setVisibility(View.GONE);
 
-                // Apply the form data setting if the API < 26.
-                if (Build.VERSION.SDK_INT < 26) {
-                    mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
-                }
+                    // Display `mainWebView` if night mode is disabled.
+                    // Because of a race condition between `applyDomainSettings` and `onPageStarted`, when night mode is set by domain settings the `WebView` may be hidden even if night mode is not
+                    // currently enabled.
+                    if (!nightMode) {
+                        nestedScrollWebView.setVisibility(View.VISIBLE);
+                    }
 
-                // Apply the font size.
-                if (fontSize == 0) {  // Apply the default font size.
-                    mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
-                } else {  // Apply the specified font size.
-                    mainWebView.getSettings().setTextZoom(fontSize);
+                    //Stop the swipe to refresh indicator if it is running
+                    swipeRefreshLayout.setRefreshing(false);
                 }
+            }
 
-                // Set third-party cookies status if API >= 21.
-                if (Build.VERSION.SDK_INT >= 21) {
-                    cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
+            // Set the favorite icon when it changes.
+            @Override
+            public void onReceivedIcon(WebView view, Bitmap icon) {
+                // Only update the favorite icon if the website has finished loading.
+                if (progressBar.getVisibility() == View.GONE) {
+                    // Save a copy of the favorite icon.
+                    // TODO.  We need to save and access the icons for each tab.
+                    favoriteIconBitmap = icon;
+
+                    tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true));
                 }
+            }
 
-                // Only set the user agent if the webpage is not currently loading.  Otherwise, changing the user agent on redirects can cause the original website to reload.
-                // <https://redmine.stoutner.com/issues/160>
-                if (!urlIsLoading) {
-                    // Set the user agent.
-                    if (userAgentName.equals(getString(R.string.system_default_user_agent))) {  // Use the system default user agent.
-                        // Get the array position of the default user agent name.
-                        int defaultUserAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
+            // Save a copy of the title when it changes.
+            @Override
+            public void onReceivedTitle(WebView view, String title) {
+                // Save a copy of the title.
+                // TODO.  Replace `webViewTitle` with `currentWebView.getTitle()`.
+                webViewTitle = title;
 
-                        // Set the user agent according to the system default.
-                        switch (defaultUserAgentArrayPosition) {
-                            case UNRECOGNIZED_USER_AGENT:  // The default user agent name is not on the canonical list.
-                                // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
-                                mainWebView.getSettings().setUserAgentString(defaultUserAgentName);
-                                break;
+                // Set the title as the tab text.
+                tabTitleTextView.setText(webViewTitle);
+            }
 
-                            case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
-                                // Set the user agent to `""`, which uses the default value.
-                                mainWebView.getSettings().setUserAgentString("");
-                                break;
+            // Enter full screen video.
+            @Override
+            public void onShowCustomView(View video, CustomViewCallback callback) {
+                // Set the full screen video flag.
+                displayingFullScreenVideo = true;
 
-                            case SETTINGS_CUSTOM_USER_AGENT:
-                                // Set the custom user agent.
-                                mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
-                                break;
+                // Pause the ad if this is the free flavor.
+                if (BuildConfig.FLAVOR.contentEquals("free")) {
+                    // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
+                    AdHelper.pauseAd(findViewById(R.id.adview));
+                }
 
-                            default:
-                                // Get the user agent string from the user agent data array
-                                mainWebView.getSettings().setUserAgentString(userAgentDataArray[defaultUserAgentArrayPosition]);
-                        }
-                    } else {  // Set the user agent according to the stored name.
-                        // Get the array position of the user agent name.
-                        int userAgentArrayPosition = userAgentNamesArray.getPosition(userAgentName);
+                // Hide the keyboard.
+                inputMethodManager.hideSoftInputFromWindow(nestedScrollWebView.getWindowToken(), 0);
 
-                        switch (userAgentArrayPosition) {
-                            case UNRECOGNIZED_USER_AGENT:  // The user agent name contains a custom user agent.
-                                mainWebView.getSettings().setUserAgentString(userAgentName);
-                                break;
+                // Hide the main content relative layout.
+                mainContentRelativeLayout.setVisibility(View.GONE);
 
-                            case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
-                                // Set the user agent to `""`, which uses the default value.
-                                mainWebView.getSettings().setUserAgentString("");
-                                break;
+                // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
+                drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
 
-                            default:
-                                // Get the user agent string from the user agent data array.
-                                mainWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
-                        }
-                    }
+                // Remove the translucent status flag.  This is necessary so the root frame layout can fill the entire screen.
+                getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
 
-                    // Store the applied user agent string, which is used in the View Source activity.
-                    appliedUserAgentString = mainWebView.getSettings().getUserAgentString();
+                /* Hide the system bars.
+                 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+                 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+                 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+                 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+                 */
+                rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+                        View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
 
-                    // Update the user agent change tracker.
-                    userAgentChanged = !appliedUserAgentString.equals(initialUserAgent);
-                }
+                // Disable the sliding drawers.
+                drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
 
-                // Set swipe to refresh.
-                switch (swipeToRefreshInt) {
-                    case DomainsDatabaseHelper.SWIPE_TO_REFRESH_SYSTEM_DEFAULT:
-                        // Set swipe to refresh according to the default.
-                        swipeRefreshLayout.setEnabled(defaultSwipeToRefresh);
-                        break;
+                // Add the video view to the full screen video frame layout.
+                fullScreenVideoFrameLayout.addView(video);
 
-                    case DomainsDatabaseHelper.SWIPE_TO_REFRESH_ENABLED:
-                        // Enable swipe to refresh.
-                        swipeRefreshLayout.setEnabled(true);
-                        break;
+                // Show the full screen video frame layout.
+                fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
+            }
 
-                    case DomainsDatabaseHelper.SWIPE_TO_REFRESH_DISABLED:
-                        // Disable swipe to refresh.
-                        swipeRefreshLayout.setEnabled(false);
-                }
+            // Exit full screen video.
+            @Override
+            public void onHideCustomView() {
+                // Unset the full screen video flag.
+                displayingFullScreenVideo = false;
 
-                // Set the loading of webpage images.
-                switch (displayWebpageImagesInt) {
-                    case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_SYSTEM_DEFAULT:
-                        mainWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
-                        break;
+                // Remove all the views from the full screen video frame layout.
+                fullScreenVideoFrameLayout.removeAllViews();
 
-                    case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_ENABLED:
-                        mainWebView.getSettings().setLoadsImagesAutomatically(true);
-                        break;
+                // Hide the full screen video frame layout.
+                fullScreenVideoFrameLayout.setVisibility(View.GONE);
 
-                    case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_DISABLED:
-                        mainWebView.getSettings().setLoadsImagesAutomatically(false);
-                        break;
-                }
+                // Enable the sliding drawers.
+                drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
 
-                // Set a green background on `urlTextBox` to indicate that custom domain settings are being used.  We have to use the deprecated `.getDrawable()` until the minimum API >= 21.
-                if (darkTheme) {
-                    urlAppBarRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_dark_blue));
-                } else {
-                    urlAppBarRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_light_green));
-                }
-            } else {  // The new URL does not have custom domain settings.  Load the defaults.
-                // Store the values from `sharedPreferences` in variables.
-                javaScriptEnabled = sharedPreferences.getBoolean("javascript", false);
-                firstPartyCookiesEnabled = sharedPreferences.getBoolean("first_party_cookies", false);
-                thirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies", false);
-                domStorageEnabled = sharedPreferences.getBoolean("dom_storage", false);
-                saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data", false);  // Form data can be removed once the minimum API >= 26.
-                easyListEnabled = sharedPreferences.getBoolean("easylist", true);
-                easyPrivacyEnabled = sharedPreferences.getBoolean("easyprivacy", true);
-                fanboysAnnoyanceListEnabled = sharedPreferences.getBoolean("fanboys_annoyance_list", true);
-                fanboysSocialBlockingListEnabled = sharedPreferences.getBoolean("fanboys_social_blocking_list", true);
-                ultraPrivacyEnabled = sharedPreferences.getBoolean("ultraprivacy", true);
-                blockAllThirdPartyRequests = sharedPreferences.getBoolean("block_all_third_party_requests", false);
+                // Show the main content relative layout.
+                mainContentRelativeLayout.setVisibility(View.VISIBLE);
 
-                // Set `javaScriptEnabled` to be `true` if `night_mode` is `true`.
-                if (nightMode) {
-                    javaScriptEnabled = true;
-                }
+                // Apply the appropriate full screen mode the `SYSTEM_UI` flags.
+                if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) {  // Privacy Browser is currently in full screen browsing mode.
+                    // Hide the app bar if specified.
+                    if (hideAppBar) {
+                        actionBar.hide();
+                    }
 
-                // Apply the default settings.
-                mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
-                cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
-                mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
-                mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
-                swipeRefreshLayout.setEnabled(defaultSwipeToRefresh);
+                    // Hide the banner ad in the free flavor.
+                    if (BuildConfig.FLAVOR.contentEquals("free")) {
+                        AdHelper.hideAd(findViewById(R.id.adview));
+                    }
 
-                // Apply the form data setting if the API < 26.
-                if (Build.VERSION.SDK_INT < 26) {
-                    mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
-                }
+                    // Remove the translucent status flag.  This is necessary so the root frame layout can fill the entire screen.
+                    getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
 
-                // Reset the pinned variables.
-                domainSettingsDatabaseId = -1;
-                pinnedSslCertificate = false;
-                pinnedSslIssuedToCName = "";
-                pinnedSslIssuedToOName = "";
-                pinnedSslIssuedToUName = "";
-                pinnedSslIssuedByCName = "";
-                pinnedSslIssuedByOName = "";
-                pinnedSslIssuedByUName = "";
-                pinnedSslStartDate = null;
-                pinnedSslEndDate = null;
-                pinnedIpAddresses = false;
-                pinnedHostIpAddresses = "";
+                    /* Hide the system bars.
+                     * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+                     * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+                     * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+                     * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+                     */
+                    rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+                            View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+                } else {  // Switch to normal viewing mode.
+                    // Remove the `SYSTEM_UI` flags from the root frame layout.
+                    rootFrameLayout.setSystemUiVisibility(0);
 
-                // Set third-party cookies status if API >= 21.
-                if (Build.VERSION.SDK_INT >= 21) {
-                    cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
+                    // Add the translucent status flag.
+                    getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
                 }
 
-                // Only set the user agent if the webpage is not currently loading.  Otherwise, changing the user agent on redirects can cause the original website to reload.
-                // <https://redmine.stoutner.com/issues/160>
-                if (!urlIsLoading) {
-                    // Get the array position of the user agent name.
-                    int userAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
-
-                    // Set the user agent.
-                    switch (userAgentArrayPosition) {
-                        case UNRECOGNIZED_USER_AGENT:  // The default user agent name is not on the canonical list.
-                            // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
-                            mainWebView.getSettings().setUserAgentString(defaultUserAgentName);
-                            break;
-
-                        case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
-                            // Set the user agent to `""`, which uses the default value.
-                            mainWebView.getSettings().setUserAgentString("");
-                            break;
-
-                        case SETTINGS_CUSTOM_USER_AGENT:
-                            // Set the custom user agent.
-                            mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
-                            break;
+                // Reload the ad for the free flavor if not in full screen mode.
+                if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
+                    // Reload the ad.
+                    AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
+                }
+            }
 
-                        default:
-                            // Get the user agent string from the user agent data array
-                            mainWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
-                    }
+            // Upload files.
+            @Override
+            public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
+                // Show the file chooser if the device is running API >= 21.
+                if (Build.VERSION.SDK_INT >= 21) {
+                    // Store the file path callback.
+                    fileChooserCallback = filePathCallback;
 
-                    // Store the applied user agent string, which is used in the View Source activity.
-                    appliedUserAgentString = mainWebView.getSettings().getUserAgentString();
+                    // Create an intent to open a chooser based ont the file chooser parameters.
+                    Intent fileChooserIntent = fileChooserParams.createIntent();
 
-                    // Update the user agent change tracker.
-                    userAgentChanged = !appliedUserAgentString.equals(initialUserAgent);
+                    // Open the file chooser.  Currently only one `startActivityForResult` exists in this activity, so the request code, used to differentiate them, is simply `0`.
+                    startActivityForResult(fileChooserIntent, 0);
                 }
-
-                // Set the loading of webpage images.
-                mainWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
-
-                // Set a transparent background on `urlTextBox`.  The deprecated `.getDrawable()` must be used until the minimum API >= 21.
-                urlAppBarRelativeLayout.setBackgroundDrawable(getResources().getDrawable(R.color.transparent));
+                return true;
             }
+        });
 
-            // Close the domains database helper.
-            domainsDatabaseHelper.close();
+        nestedScrollWebView.setWebViewClient(new WebViewClient() {
+            // `shouldOverrideUrlLoading` makes this `WebView` the default handler for URLs inside the app, so that links are not kicked out to other apps.
+            // The deprecated `shouldOverrideUrlLoading` must be used until API >= 24.
+            @SuppressWarnings("deprecation")
+            @Override
+            public boolean shouldOverrideUrlLoading(WebView view, String url) {
+                if (url.startsWith("http")) {  // Load the URL in Privacy Browser.
+                    // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
+                    formattedUrlString = "";
 
-            // Update the privacy icons, but only if `mainMenu` has already been populated.
-            if (mainMenu != null) {
-                updatePrivacyIcons(true);
-            }
-        }
+                    // Apply the domain settings for the new URL.  `applyDomainSettings` doesn't do anything if the domain has not changed.
+                    boolean userAgentChanged = applyDomainSettings(url, true, false);
 
-        // Reload the website if returning from the Domains activity.
-        if (reloadWebsite) {
-            mainWebView.reload();
-        }
+                    // Check if the user agent has changed.
+                    if (userAgentChanged) {
+                        // Manually load the URL.  The changing of the user agent will cause WebView to reload the previous URL.
+                        nestedScrollWebView.loadUrl(url, customHeaders);
 
-        // Return the user agent changed status.
-        return userAgentChanged;
-    }
+                        // Returning true indicates that Privacy Browser is manually handling the loading of the URL.
+                        return true;
+                    } else {
+                        // Returning false causes the current WebView to handle the URL and prevents it from adding redirects to the history list.
+                        return false;
+                    }
+                } else if (url.startsWith("mailto:")) {  // Load the email address in an external email program.
+                    // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
+                    Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
 
-    private void applyProxyThroughOrbot(boolean reloadWebsite) {
-        // Get a handle for the shared preferences.
-        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+                    // Parse the url and set it as the data for the intent.
+                    emailIntent.setData(Uri.parse(url));
 
-        // Get the search preferences.
-        String homepageString = sharedPreferences.getString("homepage", getString(R.string.homepage_default_value));
-        String torHomepageString = sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value));
-        String torSearchString = sharedPreferences.getString("tor_search", getString(R.string.tor_search_default_value));
-        String torSearchCustomUrlString = sharedPreferences.getString("tor_search_custom_url", getString(R.string.tor_search_custom_url_default_value));
-        String searchString = sharedPreferences.getString("search", getString(R.string.search_default_value));
-        String searchCustomUrlString = sharedPreferences.getString("search_custom_url", getString(R.string.search_custom_url_default_value));
+                    // Open the email program in a new task instead of as part of Privacy Browser.
+                    emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
-        // Get a handle for the action bar.  `getSupportActionBar()` must be used until the minimum API >= 21.
-        ActionBar actionBar = getSupportActionBar();
+                    // Make it so.
+                    startActivity(emailIntent);
 
-        // Remove the incorrect lint warning later that the action bar might be null.
-        assert actionBar != null;
+                    // Returning true indicates Privacy Browser is handling the URL by creating an intent.
+                    return true;
+                } else if (url.startsWith("tel:")) {  // Load the phone number in the dialer.
+                    // Open the dialer and load the phone number, but wait for the user to place the call.
+                    Intent dialIntent = new Intent(Intent.ACTION_DIAL);
 
-        // Set the homepage, search, and proxy options.
-        if (proxyThroughOrbot) {  // Set the Tor options.
-            // Set `torHomepageString` as `homepage`.
-            homepage = torHomepageString;
+                    // Add the phone number to the intent.
+                    dialIntent.setData(Uri.parse(url));
 
-            // If formattedUrlString is null assign the homepage to it.
-            if (formattedUrlString == null) {
-                formattedUrlString = homepage;
-            }
+                    // Open the dialer in a new task instead of as part of Privacy Browser.
+                    dialIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
-            // Set the search URL.
-            if (torSearchString.equals("Custom URL")) {  // Get the custom URL string.
-                searchURL = torSearchCustomUrlString;
-            } else {  // Use the string from the pre-built list.
-                searchURL = torSearchString;
-            }
+                    // Make it so.
+                    startActivity(dialIntent);
 
-            // Set the proxy.  `this` refers to the current activity where an `AlertDialog` might be displayed.
-            OrbotProxyHelper.setProxy(getApplicationContext(), this, "localhost", "8118");
+                    // Returning true indicates Privacy Browser is handling the URL by creating an intent.
+                    return true;
+                } else {  // Load a system chooser to select an app that can handle the URL.
+                    // Open an app that can handle the URL.
+                    Intent genericIntent = new Intent(Intent.ACTION_VIEW);
 
-            // Set the `appBar` background to indicate proxying through Orbot is enabled.  `this` refers to the context.
-            if (darkTheme) {
-                actionBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.dark_blue_30));
-            } else {
-                actionBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.blue_50));
-            }
+                    // Add the URL to the intent.
+                    genericIntent.setData(Uri.parse(url));
 
-            // Check to see if Orbot is ready.
-            if (!orbotStatus.equals("ON")) {  // Orbot is not ready.
-                // Set `waitingForOrbot`.
-                waitingForOrbot = true;
+                    // List all apps that can handle the URL instead of just opening the first one.
+                    genericIntent.addCategory(Intent.CATEGORY_BROWSABLE);
 
-                // Disable the wide view port so that the waiting for Orbot text is displayed correctly.
-                mainWebView.getSettings().setUseWideViewPort(false);
+                    // Open the app in a new task instead of as part of Privacy Browser.
+                    genericIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
-                // Load a waiting page.  `null` specifies no encoding, which defaults to ASCII.
-                mainWebView.loadData(waitingForOrbotHtmlString, "text/html", null);
-            } else if (reloadWebsite) {  // Orbot is ready and the website should be reloaded.
-                // Reload the website.
-                mainWebView.reload();
-            }
-        } else {  // Set the non-Tor options.
-            // Set `homepageString` as `homepage`.
-            homepage = homepageString;
+                    // Start the app or display a snackbar if no app is available to handle the URL.
+                    try {
+                        startActivity(genericIntent);
+                    } catch (ActivityNotFoundException exception) {
+                        Snackbar.make(nestedScrollWebView, getString(R.string.unrecognized_url) + "  " + url, Snackbar.LENGTH_SHORT).show();
+                    }
 
-            // If formattedUrlString is null assign the homepage to it.
-            if (formattedUrlString == null) {
-                formattedUrlString = homepage;
+                    // Returning true indicates Privacy Browser is handling the URL by creating an intent.
+                    return true;
+                }
             }
 
-            // Set the search URL.
-            if (searchString.equals("Custom URL")) {  // Get the custom URL string.
-                searchURL = searchCustomUrlString;
-            } else {  // Use the string from the pre-built list.
-                searchURL = searchString;
-            }
+            // Check requests against the block lists.  The deprecated `shouldInterceptRequest()` must be used until minimum API >= 21.
+            @SuppressWarnings("deprecation")
+            @Override
+            public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
+                // Create an empty web resource response to be used if the resource request is blocked.
+                WebResourceResponse emptyWebResourceResponse = new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes()));
 
-            // Reset the proxy to default.  The host is `""` and the port is `"0"`.
-            OrbotProxyHelper.setProxy(getApplicationContext(), this, "", "0");
+                // Reset the whitelist results tracker.
+                whiteListResultStringArray = null;
 
-            // Set the default `appBar` background.  `this` refers to the context.
-            if (darkTheme) {
-                actionBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_900));
-            } else {
-                actionBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_100));
-            }
+                // Initialize the third party request tracker.
+                boolean isThirdPartyRequest = false;
 
-            // Reset `waitingForOrbot.
-            waitingForOrbot = false;
+                // Initialize the current domain string.
+                String currentDomain = "";
 
-            // Reload the website if requested.
-            if (reloadWebsite) {
-                mainWebView.reload();
-            }
-        }
-    }
+                // Nobody is happy when comparing null strings.
+                if (!(formattedUrlString == null) && !(url == null)) {
+                    // Get the domain strings to URIs.
+                    Uri currentDomainUri = Uri.parse(formattedUrlString);
+                    Uri requestDomainUri = Uri.parse(url);
 
-    private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) {
-        // Get handles for the menu items.
-        MenuItem privacyMenuItem = mainMenu.findItem(R.id.toggle_javascript);
-        MenuItem firstPartyCookiesMenuItem = mainMenu.findItem(R.id.toggle_first_party_cookies);
-        MenuItem domStorageMenuItem = mainMenu.findItem(R.id.toggle_dom_storage);
-        MenuItem refreshMenuItem = mainMenu.findItem(R.id.refresh);
+                    // Get the domain host names.
+                    String currentBaseDomain = currentDomainUri.getHost();
+                    String requestBaseDomain = requestDomainUri.getHost();
 
-        // Update the privacy icon.
-        if (javaScriptEnabled) {  // JavaScript is enabled.
-            privacyMenuItem.setIcon(R.drawable.javascript_enabled);
-        } else if (firstPartyCookiesEnabled) {  // JavaScript is disabled but cookies are enabled.
-            privacyMenuItem.setIcon(R.drawable.warning);
-        } else {  // All the dangerous features are disabled.
-            privacyMenuItem.setIcon(R.drawable.privacy_mode);
-        }
+                    // Update the current domain variable.
+                    currentDomain = currentBaseDomain;
 
-        // Update the first-party cookies icon.
-        if (firstPartyCookiesEnabled) {  // First-party cookies are enabled.
-            firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_enabled);
-        } else {  // First-party cookies are disabled.
-            if (darkTheme) {
-                firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_dark);
-            } else {
-                firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_light);
-            }
-        }
+                    // Only compare the current base domain and the request base domain if neither is null.
+                    if (!(currentBaseDomain == null) && !(requestBaseDomain == null)) {
+                        // Determine the current base domain.
+                        while (currentBaseDomain.indexOf(".", currentBaseDomain.indexOf(".") + 1) > 0) {  // There is at least one subdomain.
+                            // Remove the first subdomain.
+                            currentBaseDomain = currentBaseDomain.substring(currentBaseDomain.indexOf(".") + 1);
+                        }
 
-        // Update the DOM storage icon.
-        if (javaScriptEnabled && domStorageEnabled) {  // Both JavaScript and DOM storage are enabled.
-            domStorageMenuItem.setIcon(R.drawable.dom_storage_enabled);
-        } else if (javaScriptEnabled) {  // JavaScript is enabled but DOM storage is disabled.
-            if (darkTheme) {
-                domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_dark);
-            } else {
-                domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_light);
-            }
-        } else {  // JavaScript is disabled, so DOM storage is ghosted.
-            if (darkTheme) {
-                domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_dark);
-            } else {
-                domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_light);
-            }
-        }
+                        // Determine the request base domain.
+                        while (requestBaseDomain.indexOf(".", requestBaseDomain.indexOf(".") + 1) > 0) {  // There is at least one subdomain.
+                            // Remove the first subdomain.
+                            requestBaseDomain = requestBaseDomain.substring(requestBaseDomain.indexOf(".") + 1);
+                        }
 
-        // Update the refresh icon.
-        if (darkTheme) {
-            refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
-        } else {
-            refreshMenuItem.setIcon(R.drawable.refresh_enabled_light);
-        }
+                        // Update the third party request tracker.
+                        isThirdPartyRequest = !currentBaseDomain.equals(requestBaseDomain);
+                    }
+                }
 
-        // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`.
-        if (runInvalidateOptionsMenu) {
-            invalidateOptionsMenu();
-        }
-    }
+                // Block third-party requests if enabled.
+                if (isThirdPartyRequest && blockAllThirdPartyRequests) {
+                    // Increment the blocked requests counters.
+                    blockedRequests++;
+                    thirdPartyBlockedRequests++;
 
-    private void openUrlWithExternalApp(String url) {
-        // Create a download intent.  Not specifying the action type will display the maximum number of options.
-        Intent downloadIntent = new Intent();
+                    // Update the titles of the blocklist menu items.  This must be run from the UI thread.
+                    activity.runOnUiThread(() -> {
+                        navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
+                        blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
+                        blockAllThirdPartyRequestsMenuItem.setTitle(thirdPartyBlockedRequests + " - " + getString(R.string.block_all_third_party_requests));
+                    });
 
-        // Set the URI and the MIME type.  Specifying `text/html` displays a good number of options.
-        downloadIntent.setDataAndType(Uri.parse(url), "text/html");
+                    // Add the request to the log.
+                    resourceRequests.add(new String[]{String.valueOf(REQUEST_THIRD_PARTY), url});
 
-        // Flag the intent to open in a new task.
-        downloadIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    // Return an empty web resource response.
+                    return emptyWebResourceResponse;
+                }
 
-        // Show the chooser.
-        startActivity(Intent.createChooser(downloadIntent, getString(R.string.open_with)));
-    }
+                // Check UltraPrivacy if it is enabled.
+                if (ultraPrivacyEnabled) {
+                    if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, ultraPrivacy)) {
+                        // Increment the blocked requests counters.
+                        blockedRequests++;
+                        ultraPrivacyBlockedRequests++;
 
-    private void highlightUrlText() {
-        // Only highlight the URL text if the box is not currently selected.
-        if (!urlTextBox.hasFocus()) {
-            // Get the URL string.
-            String urlString = urlTextBox.getText().toString();
+                        // Update the titles of the blocklist menu items.  This must be run from the UI thread.
+                        activity.runOnUiThread(() -> {
+                            navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
+                            blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
+                            ultraPrivacyMenuItem.setTitle(ultraPrivacyBlockedRequests + " - " + getString(R.string.ultraprivacy));
+                        });
 
-            // Highlight the URL according to the protocol.
-            if (urlString.startsWith("file://")) {  // This is a file URL.
-                // De-emphasize only the protocol.
-                urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-            } else if (urlString.startsWith("content://")) {
-                // De-emphasize only the protocol.
-                urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 10, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-            } else {  // This is a web URL.
-                // Get the index of the `/` immediately after the domain name.
-                int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2));
+                        // The resource request was blocked.  Return an empty web resource response.
+                        return emptyWebResourceResponse;
+                    }
 
-                // Create a base URL string.
-                String baseUrl;
+                    // If the whitelist result is not null, the request has been allowed by UltraPrivacy.
+                    if (whiteListResultStringArray != null) {
+                        // Add a whitelist entry to the resource requests array.
+                        resourceRequests.add(whiteListResultStringArray);
 
-                // Get the base URL.
-                if (endOfDomainName > 0) {  // There is at least one character after the base URL.
-                    // Get the base URL.
-                    baseUrl = urlString.substring(0, endOfDomainName);
-                } else {  // There are no characters after the base URL.
-                    // Set the base URL to be the entire URL string.
-                    baseUrl = urlString;
+                        // The resource request has been allowed by UltraPrivacy.  `return null` loads the requested resource.
+                        return null;
+                    }
                 }
 
-                // Get the index of the last `.` in the domain.
-                int lastDotIndex = baseUrl.lastIndexOf(".");
+                // Check EasyList if it is enabled.
+                if (easyListEnabled) {
+                    if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, easyList)) {
+                        // Increment the blocked requests counters.
+                        blockedRequests++;
+                        easyListBlockedRequests++;
 
-                // Get the index of the penultimate `.` in the domain.
-                int penultimateDotIndex = baseUrl.lastIndexOf(".", lastDotIndex - 1);
+                        // Update the titles of the blocklist menu items.  This must be run from the UI thread.
+                        activity.runOnUiThread(() -> {
+                            navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
+                            blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
+                            easyListMenuItem.setTitle(easyListBlockedRequests + " - " + getString(R.string.easylist));
+                        });
 
-                // Markup the beginning of the URL.
-                if (urlString.startsWith("http://")) {  // Highlight the protocol of connections that are not encrypted.
-                    urlTextBox.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                        // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
+                        whiteListResultStringArray = null;
 
-                    // De-emphasize subdomains.
-                    if (penultimateDotIndex > 0) {  // There is more than one subdomain in the domain name.
-                        urlTextBox.getText().setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-                    }
-                } else if (urlString.startsWith("https://")) {  // De-emphasize the protocol of connections that are encrypted.
-                    if (penultimateDotIndex > 0) {  // There is more than one subdomain in the domain name.
-                        // De-emphasize the protocol and the additional subdomains.
-                        urlTextBox.getText().setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-                    } else {  // There is only one subdomain in the domain name.
-                        // De-emphasize only the protocol.
-                        urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                        // The resource request was blocked.  Return an empty web resource response.
+                        return emptyWebResourceResponse;
                     }
                 }
 
-                // De-emphasize the text after the domain name.
-                if (endOfDomainName > 0) {
-                    urlTextBox.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                // Check EasyPrivacy if it is enabled.
+                if (easyPrivacyEnabled) {
+                    if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, easyPrivacy)) {
+                        // Increment the blocked requests counters.
+                        blockedRequests++;
+                        easyPrivacyBlockedRequests++;
+
+                        // Update the titles of the blocklist menu items.  This must be run from the UI thread.
+                        activity.runOnUiThread(() -> {
+                            navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
+                            blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
+                            easyPrivacyMenuItem.setTitle(easyPrivacyBlockedRequests + " - " + getString(R.string.easyprivacy));
+                        });
+
+                        // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
+                        whiteListResultStringArray = null;
+
+                        // The resource request was blocked.  Return an empty web resource response.
+                        return emptyWebResourceResponse;
+                    }
                 }
-            }
-        }
-    }
 
-    private void loadBookmarksFolder() {
-        // Update the bookmarks cursor with the contents of the bookmarks database for the current folder.
-        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
+                // Check Fanboy’s Annoyance List if it is enabled.
+                if (fanboysAnnoyanceListEnabled) {
+                    if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, fanboysAnnoyanceList)) {
+                        // Increment the blocked requests counters.
+                        blockedRequests++;
+                        fanboysAnnoyanceListBlockedRequests++;
 
-        // Populate the bookmarks cursor adapter.  `this` specifies the `Context`.  `false` disables `autoRequery`.
-        bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
-            @Override
-            public View newView(Context context, Cursor cursor, ViewGroup parent) {
-                // Inflate the individual item layout.  `false` does not attach it to the root.
-                return getLayoutInflater().inflate(R.layout.bookmarks_drawer_item_linearlayout, parent, false);
-            }
+                        // Update the titles of the blocklist menu items.  This must be run from the UI thread.
+                        activity.runOnUiThread(() -> {
+                            navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
+                            blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
+                            fanboysAnnoyanceListMenuItem.setTitle(fanboysAnnoyanceListBlockedRequests + " - " + getString(R.string.fanboys_annoyance_list));
+                        });
 
-            @Override
-            public void bindView(View view, Context context, Cursor cursor) {
-                // Get handles for the views.
-                ImageView bookmarkFavoriteIcon = view.findViewById(R.id.bookmark_favorite_icon);
-                TextView bookmarkNameTextView = view.findViewById(R.id.bookmark_name);
+                        // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
+                        whiteListResultStringArray = null;
 
-                // Get the favorite icon byte array from the cursor.
-                byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON));
+                        // The resource request was blocked.  Return an empty web resource response.
+                        return emptyWebResourceResponse;
+                    }
+                } else if (fanboysSocialBlockingListEnabled) {  // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled.
+                    if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, fanboysSocialList)) {
+                        // Increment the blocked requests counters.
+                        blockedRequests++;
+                        fanboysSocialBlockingListBlockedRequests++;
 
-                // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
-                Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
+                        // Update the titles of the blocklist menu items.  This must be run from the UI thread.
+                        activity.runOnUiThread(() -> {
+                            navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
+                            blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
+                            fanboysSocialBlockingListMenuItem.setTitle(fanboysSocialBlockingListBlockedRequests + " - " + getString(R.string.fanboys_social_blocking_list));
+                        });
 
-                // Display the bitmap in `bookmarkFavoriteIcon`.
-                bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap);
+                        // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
+                        whiteListResultStringArray = null;
 
-                // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`.
-                String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
-                bookmarkNameTextView.setText(bookmarkNameString);
+                        // The resource request was blocked.  Return an empty web resource response.
+                        return emptyWebResourceResponse;
+                    }
+                }
 
-                // Make the font bold for folders.
-                if (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {
-                    bookmarkNameTextView.setTypeface(Typeface.DEFAULT_BOLD);
-                } else {  // Reset the font to default for normal bookmarks.
-                    bookmarkNameTextView.setTypeface(Typeface.DEFAULT);
+                // Add the request to the log because it hasn't been processed by any of the previous checks.
+                if (whiteListResultStringArray != null) {  // The request was processed by a whitelist.
+                    resourceRequests.add(whiteListResultStringArray);
+                } else {  // The request didn't match any blocklist entry.  Log it as a default request.
+                    resourceRequests.add(new String[]{String.valueOf(REQUEST_DEFAULT), url});
                 }
+
+                // The resource request has not been blocked.  `return null` loads the requested resource.
+                return null;
             }
-        };
 
-        // Populate the `ListView` with the adapter.
-        bookmarksListView.setAdapter(bookmarksCursorAdapter);
+            // Handle HTTP authentication requests.
+            @Override
+            public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
+                // Store `handler` so it can be accessed from `onHttpAuthenticationCancel()` and `onHttpAuthenticationProceed()`.
+                httpAuthHandler = handler;
 
-        // Set the bookmarks drawer title.
-        if (currentBookmarksFolder.isEmpty()) {
-            bookmarksTitleTextView.setText(R.string.bookmarks);
-        } else {
-            bookmarksTitleTextView.setText(currentBookmarksFolder);
-        }
-    }
+                // Display the HTTP authentication dialog.
+                DialogFragment httpAuthenticationDialogFragment = HttpAuthenticationDialog.displayDialog(host, realm);
+                httpAuthenticationDialogFragment.show(fragmentManager, getString(R.string.http_authentication));
+            }
 
-    private void openWithApp(String url) {
-        // Create the open with intent with `ACTION_VIEW`.
-        Intent openWithAppIntent = new Intent(Intent.ACTION_VIEW);
+            // Update the URL in urlTextBox when the page starts to load.
+            @Override
+            public void onPageStarted(WebView view, String url, Bitmap favicon) {
+                // Set `urlIsLoading` to `true`, so that redirects while loading do not trigger changes in the user agent, which forces another reload of the existing page.
+                // This is also used to determine when to check for pinned mismatches.
+                urlIsLoading = true;
 
-        // Set the URI but not the MIME type.  This should open all available apps.
-        openWithAppIntent.setData(Uri.parse(url));
+                // Reset the list of host IP addresses.
+                currentHostIpAddresses = "";
 
-        // Flag the intent to open in a new task.
-        openWithAppIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                // Reset the list of resource requests.
+                resourceRequests.clear();
 
-        // Show the chooser.
-        startActivity(openWithAppIntent);
-    }
+                // Initialize the counters for requests blocked by each blocklist.
+                blockedRequests = 0;
+                easyListBlockedRequests = 0;
+                easyPrivacyBlockedRequests = 0;
+                fanboysAnnoyanceListBlockedRequests = 0;
+                fanboysSocialBlockingListBlockedRequests = 0;
+                ultraPrivacyBlockedRequests = 0;
+                thirdPartyBlockedRequests = 0;
 
-    private void openWithBrowser(String url) {
-        // Create the open with intent with `ACTION_VIEW`.
-        Intent openWithBrowserIntent = new Intent(Intent.ACTION_VIEW);
+                // If night mode is enabled, hide `mainWebView` until after the night mode CSS is applied.
+                if (nightMode) {
+                    nestedScrollWebView.setVisibility(View.INVISIBLE);
+                }
 
-        // Set the URI and the MIME type.  `"text/html"` should load browser options.
-        openWithBrowserIntent.setDataAndType(Uri.parse(url), "text/html");
+                // Hide the keyboard.
+                inputMethodManager.hideSoftInputFromWindow(nestedScrollWebView.getWindowToken(), 0);
+
+                // Check to see if Privacy Browser is waiting on Orbot.
+                if (!waitingForOrbot) {  // Process the URL.
+                    // The formatted URL string must be updated at the beginning of the load, so that if the user toggles JavaScript during the load the new website is reloaded.
+                    formattedUrlString = url;
+
+                    // Display the formatted URL text.
+                    urlTextBox.setText(formattedUrlString);
 
-        // Flag the intent to open in a new task.
-        openWithBrowserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    // Apply text highlighting to `urlTextBox`.
+                    highlightUrlText();
 
-        // Show the chooser.
-        startActivity(openWithBrowserIntent);
-    }
+                    // Get a URI for the current URL.
+                    Uri currentUri = Uri.parse(formattedUrlString);
 
-    private static void checkPinnedMismatch() {
-        if ((pinnedSslCertificate || pinnedIpAddresses) && !ignorePinnedDomainInformation) {
-            // Initialize the current SSL certificate variables.
-            String currentWebsiteIssuedToCName = "";
-            String currentWebsiteIssuedToOName = "";
-            String currentWebsiteIssuedToUName = "";
-            String currentWebsiteIssuedByCName = "";
-            String currentWebsiteIssuedByOName = "";
-            String currentWebsiteIssuedByUName = "";
-            Date currentWebsiteSslStartDate = null;
-            Date currentWebsiteSslEndDate = null;
+                    // Get the IP addresses for the host.
+                    new GetHostIpAddresses(activity).execute(currentUri.getHost());
 
+                    // Apply any custom domain settings if the URL was loaded by navigating history.
+                    if (navigatingHistory) {
+                        // Apply the domain settings.
+                        boolean userAgentChanged = applyDomainSettings(url, true, false);
 
-            // Extract the individual pieces of information from the current website SSL certificate if it is not null.
-            if (sslCertificate != null) {
-                currentWebsiteIssuedToCName = sslCertificate.getIssuedTo().getCName();
-                currentWebsiteIssuedToOName = sslCertificate.getIssuedTo().getOName();
-                currentWebsiteIssuedToUName = sslCertificate.getIssuedTo().getUName();
-                currentWebsiteIssuedByCName = sslCertificate.getIssuedBy().getCName();
-                currentWebsiteIssuedByOName = sslCertificate.getIssuedBy().getOName();
-                currentWebsiteIssuedByUName = sslCertificate.getIssuedBy().getUName();
-                currentWebsiteSslStartDate = sslCertificate.getValidNotBeforeDate();
-                currentWebsiteSslEndDate = sslCertificate.getValidNotAfterDate();
-            }
+                        // Reset `navigatingHistory`.
+                        navigatingHistory = false;
 
-            // Initialize string variables to store the SSL certificate dates.  Strings are needed to compare the values below, which doesn't work with `Dates` if they are `null`.
-            String currentWebsiteSslStartDateString = "";
-            String currentWebsiteSslEndDateString = "";
-            String pinnedSslStartDateString = "";
-            String pinnedSslEndDateString = "";
+                        // Manually load the URL if the user agent has changed, which will have caused the previous URL to be reloaded.
+                        if (userAgentChanged) {
+                            loadUrl(formattedUrlString);
+                        }
+                    }
 
-            // Convert the `Dates` to `Strings` if they are not `null`.
-            if (currentWebsiteSslStartDate != null) {
-                currentWebsiteSslStartDateString = currentWebsiteSslStartDate.toString();
-            }
+                    // Replace Refresh with Stop if the menu item has been created.  (The WebView typically begins loading before the menu items are instantiated.)
+                    if (refreshMenuItem != null) {
+                        // Set the title.
+                        refreshMenuItem.setTitle(R.string.stop);
 
-            if (currentWebsiteSslEndDate != null) {
-                currentWebsiteSslEndDateString = currentWebsiteSslEndDate.toString();
+                        // If the icon is displayed in the AppBar, set it according to the theme.
+                        if (displayAdditionalAppBarIcons) {
+                            if (darkTheme) {
+                                refreshMenuItem.setIcon(R.drawable.close_dark);
+                            } else {
+                                refreshMenuItem.setIcon(R.drawable.close_light);
+                            }
+                        }
+                    }
+                }
             }
 
-            if (pinnedSslStartDate != null) {
-                pinnedSslStartDateString = pinnedSslStartDate.toString();
-            }
+            // It is necessary to update `formattedUrlString` and `urlTextBox` after the page finishes loading because the final URL can change during load.
+            @Override
+            public void onPageFinished(WebView view, String url) {
+                // Reset the wide view port if it has been turned off by the waiting for Orbot message.
+                if (!waitingForOrbot) {
+                    // Only use a wide view port if the URL starts with `http`, not for `file://` and `content://`.
+                    nestedScrollWebView.getSettings().setUseWideViewPort(url.startsWith("http"));
+                }
 
-            if (pinnedSslEndDate != null) {
-                pinnedSslEndDateString = pinnedSslEndDate.toString();
-            }
+                // Flush any cookies to persistent storage.  `CookieManager` has become very lazy about flushing cookies in recent versions.
+                if (firstPartyCookiesEnabled && Build.VERSION.SDK_INT >= 21) {
+                    cookieManager.flush();
+                }
 
-            // Check to see if the pinned information matches the current information.
-            if ((pinnedIpAddresses && !currentHostIpAddresses.equals(pinnedHostIpAddresses)) || (pinnedSslCertificate && (!currentWebsiteIssuedToCName.equals(pinnedSslIssuedToCName) ||
-                    !currentWebsiteIssuedToOName.equals(pinnedSslIssuedToOName) || !currentWebsiteIssuedToUName.equals(pinnedSslIssuedToUName) ||
-                    !currentWebsiteIssuedByCName.equals(pinnedSslIssuedByCName) || !currentWebsiteIssuedByOName.equals(pinnedSslIssuedByOName) ||
-                    !currentWebsiteIssuedByUName.equals(pinnedSslIssuedByUName) || !currentWebsiteSslStartDateString.equals(pinnedSslStartDateString) ||
-                    !currentWebsiteSslEndDateString.equals(pinnedSslEndDateString)))) {
+                // Update the Refresh menu item if it has been created.
+                if (refreshMenuItem != null) {
+                    // Reset the Refresh title.
+                    refreshMenuItem.setTitle(R.string.refresh);
 
-                // Get a handle for the pinned mismatch alert dialog.
-                DialogFragment pinnedMismatchDialogFragment = PinnedMismatchDialog.displayDialog(pinnedSslCertificate, pinnedIpAddresses);
+                    // If the icon is displayed in the AppBar, reset it according to the theme.
+                    if (displayAdditionalAppBarIcons) {
+                        if (darkTheme) {
+                            refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
+                        } else {
+                            refreshMenuItem.setIcon(R.drawable.refresh_enabled_light);
+                        }
+                    }
+                }
 
-                // Show the pinned mismatch alert dialog.
-                pinnedMismatchDialogFragment.show(fragmentManager, "Pinned Mismatch");
-            }
-        }
-    }
 
-    // This must run asynchronously because it involves a network request.  `String` declares the parameters.  `Void` does not declare progress units.  `String` contains the results.
-    private static class GetHostIpAddresses extends AsyncTask<String, Void, String> {
-        // The weak references are used to determine if the activity have disappeared while the AsyncTask is running.
-        private final WeakReference<Activity> activityWeakReference;
+                // Clear the cache and history if Incognito Mode is enabled.
+                if (incognitoModeEnabled) {
+                    // Clear the cache.  `true` includes disk files.
+                    nestedScrollWebView.clearCache(true);
 
-        GetHostIpAddresses(Activity activity) {
-            // Populate the weak references.
-            activityWeakReference = new WeakReference<>(activity);
-        }
+                    // Clear the back/forward history.
+                    nestedScrollWebView.clearHistory();
 
-        // `onPreExecute()` operates on the UI thread.
-        @Override
-        protected void onPreExecute() {
-            // Get a handle for the activity.
-            Activity activity = activityWeakReference.get();
+                    // Manually delete cache folders.
+                    try {
+                        // Delete the main cache directory.
+                        privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache");
 
-            // Abort if the activity is gone.
-            if ((activity == null) || activity.isFinishing()) {
-                return;
-            }
+                        // Delete the secondary `Service Worker` cache directory.
+                        // A `String[]` must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
+                        privacyBrowserRuntime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
+                    } catch (IOException e) {
+                        // Do nothing if an error is thrown.
+                    }
+                }
 
-            // Set the getting IP addresses tracker.
-            gettingIpAddresses = true;
-        }
+                // Update the URL text box and apply domain settings if not waiting on Orbot.
+                if (!waitingForOrbot) {
+                    // Check to see if `WebView` has set `url` to be `about:blank`.
+                    if (url.equals("about:blank")) {  // `WebView` is blank, so `formattedUrlString` should be `""` and `urlTextBox` should display a hint.
+                        // Set `formattedUrlString` to `""`.
+                        formattedUrlString = "";
 
+                        urlTextBox.setText(formattedUrlString);
 
-        @Override
-        protected String doInBackground(String... domainName) {
-            // Get a handle for the activity.
-            Activity activity = activityWeakReference.get();
+                        // Request focus for `urlTextBox`.
+                        urlTextBox.requestFocus();
 
-            // Abort if the activity is gone.
-            if ((activity == null) || activity.isFinishing()) {
-                // Return an empty spannable string builder.
-                return "";
-            }
+                        // Display the keyboard.
+                        inputMethodManager.showSoftInput(urlTextBox, 0);
 
-            // Initialize an IP address string builder.
-            StringBuilder ipAddresses = new StringBuilder();
+                        // Apply the domain settings.  This clears any settings from the previous domain.
+                        applyDomainSettings(formattedUrlString, true, false);
+                    } else {  // `WebView` has loaded a webpage.
+                        // Set the formatted URL string.  Getting the URL from the WebView instead of using the one provided by `onPageFinished` makes websites like YouTube function correctly.
+                        formattedUrlString = nestedScrollWebView.getUrl();
 
-            // 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]);
+                        // Only update the URL text box if the user is not typing in it.
+                        if (!urlTextBox.hasFocus()) {
+                            // Display the formatted URL text.
+                            urlTextBox.setText(formattedUrlString);
 
-                // Add each IP address to the string builder.
-                for (InetAddress inetAddress : inetAddressesArray) {
-                    if (ipAddresses.length() == 0) {  // This is the first IP address.
-                        // Add the IP address to the string builder.
-                        ipAddresses.append(inetAddress.getHostAddress());
-                    } else {  // This is not the first IP address.
-                        // Add a line break to the string builder first.
-                        ipAddresses.append("\n");
+                            // Apply text highlighting to `urlTextBox`.
+                            highlightUrlText();
+                        }
+                    }
 
-                        // Add the IP address to the string builder.
-                        ipAddresses.append(inetAddress.getHostAddress());
+                    // Store the SSL certificate so it can be accessed from `ViewSslCertificateDialog` and `PinnedMismatchDialog`.
+                    sslCertificate = nestedScrollWebView.getCertificate();
+
+                    // Check the current website information against any pinned domain information if the current IP addresses have been loaded.
+                    if (!gettingIpAddresses) {
+                        checkPinnedMismatch();
                     }
                 }
-            } catch (UnknownHostException exception) {
-                // Do nothing.
+
+                // Reset `urlIsLoading`, which is used to prevent reloads on redirect if the user agent changes.  It is also used to determine when to check for pinned mismatches.
+                urlIsLoading = false;
             }
 
-            // Return the string.
-            return ipAddresses.toString();
-        }
+            // Handle SSL Certificate errors.
+            @Override
+            public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
+                // Get the current website SSL certificate.
+                SslCertificate currentWebsiteSslCertificate = error.getCertificate();
 
-        // `onPostExecute()` operates on the UI thread.
-        @Override
-        protected void onPostExecute(String ipAddresses) {
-            // Get a handle for the activity.
-            Activity activity = activityWeakReference.get();
+                // Extract the individual pieces of information from the current website SSL certificate.
+                String currentWebsiteIssuedToCName = currentWebsiteSslCertificate.getIssuedTo().getCName();
+                String currentWebsiteIssuedToOName = currentWebsiteSslCertificate.getIssuedTo().getOName();
+                String currentWebsiteIssuedToUName = currentWebsiteSslCertificate.getIssuedTo().getUName();
+                String currentWebsiteIssuedByCName = currentWebsiteSslCertificate.getIssuedBy().getCName();
+                String currentWebsiteIssuedByOName = currentWebsiteSslCertificate.getIssuedBy().getOName();
+                String currentWebsiteIssuedByUName = currentWebsiteSslCertificate.getIssuedBy().getUName();
+                Date currentWebsiteSslStartDate = currentWebsiteSslCertificate.getValidNotBeforeDate();
+                Date currentWebsiteSslEndDate = currentWebsiteSslCertificate.getValidNotAfterDate();
 
-            // Abort if the activity is gone.
-            if ((activity == null) || activity.isFinishing()) {
-                return;
-            }
+                // Proceed to the website if the current SSL website certificate matches the pinned domain certificate.
+                if (pinnedSslCertificate &&
+                        currentWebsiteIssuedToCName.equals(pinnedSslIssuedToCName) && currentWebsiteIssuedToOName.equals(pinnedSslIssuedToOName) &&
+                        currentWebsiteIssuedToUName.equals(pinnedSslIssuedToUName) && currentWebsiteIssuedByCName.equals(pinnedSslIssuedByCName) &&
+                        currentWebsiteIssuedByOName.equals(pinnedSslIssuedByOName) && currentWebsiteIssuedByUName.equals(pinnedSslIssuedByUName) &&
+                        currentWebsiteSslStartDate.equals(pinnedSslStartDate) && currentWebsiteSslEndDate.equals(pinnedSslEndDate)) {
 
-            // Store the IP addresses.
-            currentHostIpAddresses = ipAddresses;
+                    // An SSL certificate is pinned and matches the current domain certificate.  Proceed to the website without displaying an error.
+                    handler.proceed();
+                } else {  // Either there isn't a pinned SSL certificate or it doesn't match the current website certificate.
+                    // Store `handler` so it can be accesses from `onSslErrorCancel()` and `onSslErrorProceed()`.
+                    sslErrorHandler = handler;
 
-            if (!urlIsLoading) {
-                checkPinnedMismatch();
+                    // Display the SSL error `AlertDialog`.
+                    DialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error);
+                    sslCertificateErrorDialogFragment.show(fragmentManager, getString(R.string.ssl_certificate_error));
+                }
             }
+        });
 
-            // Reset the getting IP addresses tracker.
-            gettingIpAddresses = false;
+        // Check to see if this is the first tab.
+        if (tabNumber == 0) {
+            // Set this nested scroll WebView as the current WebView.
+            currentWebView = nestedScrollWebView;
+
+            // Apply the app settings from the shared preferences.
+            applyAppSettings();
+
+            // Load the website if not waiting for Orbot to connect.
+            if (!waitingForOrbot) {
+                loadUrl(formattedUrlString);
+            }
         }
     }
 }
\ No newline at end of file
index cfba553e4f9587cc97fdb947af60cd4baac34361..4d84d1de5b3385d9f64c74733a2e4f4e32bc5aac 100644 (file)
@@ -52,7 +52,7 @@ public class AddDomainDialog extends DialogFragment {
     // `addDomainListener` is used in `onAttach()` and `onCreateDialog()`.
     private AddDomainListener addDomainListener;
 
-
+    @Override
     public void onAttach(Context context) {
         // Run the default commands.
         super.onAttach(context);
index 91cb7dc6ba5ecdd0eb362cbc84907cfee23f4a6e..5c811e8d42ef1e6202f0e1adbd8f3dfb41054976 100644 (file)
@@ -52,6 +52,7 @@ import java.text.DateFormat;
 import java.util.Date;
 
 public class AboutTabFragment extends Fragment {
+    // Track the current tab number.
     private int tabNumber;
 
     // Store the tab number in the arguments bundle.
@@ -83,7 +84,7 @@ public class AboutTabFragment extends Fragment {
     }
 
     @Override
-    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+    public View onCreateView(@NonNull LayoutInflater layoutInflater, ViewGroup container, Bundle savedInstanceState) {
         // Create a tab layout view.
         View tabLayout;
 
@@ -94,7 +95,7 @@ public class AboutTabFragment extends Fragment {
         // Load the tabs.  Tab numbers start at 0.
         if (tabNumber == 0) {  // Load the about tab.
             // Setting false at the end of inflater.inflate does not attach the inflated layout as a child of container.  The fragment will take care of attaching the root automatically.
-            tabLayout = inflater.inflate(R.layout.about_tab_version, container, false);
+            tabLayout = layoutInflater.inflate(R.layout.about_tab_version, container, false);
 
             // Get handles for the `TextViews`.
             TextView versionTextView = tabLayout.findViewById(R.id.version);
@@ -147,7 +148,7 @@ public class AboutTabFragment extends Fragment {
             String signatureAlgorithmLabel = getString(R.string.signature_algorithm) + "  ";
 
             // `webViewLayout` is only used to get the default user agent from `bare_webview`.  It is not used to render content on the screen.
-            View webViewLayout = inflater.inflate(R.layout.bare_webview, container, false);
+            View webViewLayout = layoutInflater.inflate(R.layout.bare_webview, container, false);
             WebView tabLayoutWebView = webViewLayout.findViewById(R.id.bare_webview);
             String userAgentString =  tabLayoutWebView.getSettings().getUserAgentString();
 
@@ -339,14 +340,14 @@ public class AboutTabFragment extends Fragment {
             }
         } else { // load a `WebView` for all the other tabs.  Tab numbers start at 0.
             // Setting false at the end of inflater.inflate does not attach the inflated layout as a child of container.  The fragment will take care of attaching the root automatically.
-            tabLayout = inflater.inflate(R.layout.bare_webview, container, false);
+            tabLayout = layoutInflater.inflate(R.layout.bare_webview, container, false);
 
             // Get a handle for `tabWebView`.
             WebView tabWebView = (WebView) tabLayout;
 
             // Load the tabs according to the theme.
             if (MainWebViewActivity.darkTheme) {  // The dark theme is applied.
-                // Set the background color.  We have to use the deprecated `.getColor()` until API >= 23.
+                // Set the background color.  The deprecated `.getColor()` must be used until the minimum API >= 23.
                 //noinspection deprecation
                 tabWebView.setBackgroundColor(getResources().getColor(R.color.gray_850));
 
diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/WebViewTabFragment.java b/app/src/main/java/com/stoutner/privacybrowser/fragments/WebViewTabFragment.java
new file mode 100644 (file)
index 0000000..8ad76d7
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * Copyright © 2019 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.fragments;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+
+import com.stoutner.privacybrowser.R;
+import com.stoutner.privacybrowser.views.NestedScrollWebView;
+
+public class WebViewTabFragment extends Fragment {
+    // The public interface is used to send information back to the parent activity.
+    public interface NewTabListener {
+        void initializeWebView(NestedScrollWebView nestedScrollWebView, int tabNumber);
+    }
+
+    // The new tab listener is used in `onAttach()` and `onCreateView()`.
+    private NewTabListener newTabListener;
+
+    @Override
+    public void onAttach(Context context) {
+        // Run the default commands.
+        super.onAttach(context);
+
+        // Get a handle for the new tab listener from the launching context.
+        newTabListener = (NewTabListener) context;
+    }
+
+    public static WebViewTabFragment createTab(int tabNumber) {
+        // Create a bundle.
+        Bundle bundle = new Bundle();
+
+        // Store the tab number in the bundle.
+        bundle.putInt("tab_number", tabNumber);
+
+        // Create a new instance of the WebView tab fragment.
+        WebViewTabFragment webViewTabFragment = new WebViewTabFragment();
+
+        // Add the bundle to the fragment.
+        webViewTabFragment.setArguments(bundle);
+
+        // Return the new fragment.
+        return webViewTabFragment;
+    }
+
+    @Override
+    public View onCreateView(@NonNull LayoutInflater layoutInflater, ViewGroup container, Bundle savedInstanceState) {
+        // Get the arguments.
+        Bundle arguments = getArguments();
+
+        // Remove the incorrect lint warning that the arguments might be null.
+        assert arguments != null;
+
+        // Get the variables from the arguments
+        int tabNumber = arguments.getInt("tab_number");
+
+        // Inflate the tab's WebView.  Setting false at the end of inflater.inflate does not attach the inflated layout as a child of container.  The fragment will take care of attaching the root automatically.
+        View tabView = layoutInflater.inflate(R.layout.nestedscroll_webview, container, false);
+
+        // Get a handle for the nested scroll WebView.
+        NestedScrollWebView nestedScrollWebView = tabView.findViewById(R.id.nestedscroll_webview);
+
+        // Request the main activity initialize the WebView.
+        newTabListener.initializeWebView(nestedScrollWebView, tabNumber);
+
+        // Return the tab view.
+        return tabView;
+    }
+}
\ No newline at end of file
index 80ef04065f39e31b1253b81316c9dd620c5f2f02..2e9773a2c4f920c1292ec874aac753f76aa7a46e 100644 (file)
@@ -1585,7 +1585,7 @@ public class BlockListHelper {
     }
 
     public boolean isBlocked(String currentDomain, String resourceUrl, boolean isThirdPartyRequest, ArrayList<List<String[]>> blockList) {
-        // Get the block list name.
+        // Get the blocklist name.
         String BLOCK_LIST_NAME_STRING = blockList.get(0).get(1)[0];
 
         // Assert that currentDomain != null only if this is a third party request.  Apparently, lint can't tell that this isn't redundant.
diff --git a/app/src/main/res/drawable/tab.xml b/app/src/main/res/drawable/tab.xml
new file mode 100644 (file)
index 0000000..de04901
--- /dev/null
@@ -0,0 +1,18 @@
+<!-- `tab.xml` comes from the Android Material icon set, where it is called `tab`.  It is released under the Apache License 2.0. -->
+
+<!-- `tools:ignore="VectorRaster"` removes the lint warning about `android:autoMirrored="true"` not applying to API < 21. -->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:height="24dp"
+    android:width="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0"
+    android:autoMirrored="true"
+    tools:ignore="VectorRaster" >
+
+    <!-- A hard coded color must be used until the minimum API >= 21.  Then `@color` can be used. -->
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,19L3,19L3,5h10v4h8v10z" />
+</vector>
\ No newline at end of file
index 3a717ce79321a5c95372714adeb174177b5581fb..d43059026b8c2dd931420764ba707553014d51b0 100644 (file)
@@ -4,15 +4,15 @@
 <vector
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
-    android:autoMirrored="true"
     android:height="24dp"
     android:width="24dp"
     android:viewportWidth="24.0"
     android:viewportHeight="24.0"
+    android:autoMirrored="true"
     tools:ignore="VectorRaster" >
 
-    <!-- A hard coded color must be used until API >= 21.  Then `@color` can be used. -->
+    <!-- A hard coded color must be used until the minimum API >= 21.  Then `@color` can be used. -->
     <path
         android:fillColor="#FF616161"
         android:pathData="M2.53,19.65l1.34,0.56v-9.03l-2.43,5.86c-0.41,1.02 0.08,2.19 1.09,2.61zM22.03,15.95L17.07,3.98c-0.31,-0.75 -1.04,-1.21 -1.81,-1.23 -0.26,0 -0.53,0.04 -0.79,0.15L7.1,5.95c-0.75,0.31 -1.21,1.03 -1.23,1.8 -0.01,0.27 0.04,0.54 0.15,0.8l4.96,11.97c0.31,0.76 1.05,1.22 1.83,1.23 0.26,0 0.52,-0.05 0.77,-0.15l7.36,-3.05c1.02,-0.42 1.51,-1.59 1.09,-2.6zM7.88,8.75c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1zM5.88,19.75c0,1.1 0.9,2 2,2h1.45l-3.45,-8.34v6.34z"/>
-</vector>
+</vector>
\ No newline at end of file
index c1409d0deddf79ddf89f9313dc9c09053cea3b84..33193f1e592cb030ef705242e7a8b62782446413 100644 (file)
@@ -4,15 +4,15 @@
 <vector
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
-    android:autoMirrored="true"
     android:height="24dp"
     android:width="24dp"
     android:viewportWidth="24.0"
     android:viewportHeight="24.0"
+    android:autoMirrored="true"
     tools:ignore="VectorRaster" >
 
-    <!-- A hard coded color must be used until API >= 21.  Then `@color` can be used. -->
+    <!-- A hard coded color must be used until the minimum API >= 21.  Then `@color` can be used. -->
     <path
         android:fillColor="#FF1565C0"
         android:pathData="M2.53,19.65l1.34,0.56v-9.03l-2.43,5.86c-0.41,1.02 0.08,2.19 1.09,2.61zM22.03,15.95L17.07,3.98c-0.31,-0.75 -1.04,-1.21 -1.81,-1.23 -0.26,0 -0.53,0.04 -0.79,0.15L7.1,5.95c-0.75,0.31 -1.21,1.03 -1.23,1.8 -0.01,0.27 0.04,0.54 0.15,0.8l4.96,11.97c0.31,0.76 1.05,1.22 1.83,1.23 0.26,0 0.52,-0.05 0.77,-0.15l7.36,-3.05c1.02,-0.42 1.51,-1.59 1.09,-2.6zM7.88,8.75c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1zM5.88,19.75c0,1.1 0.9,2 2,2h1.45l-3.45,-8.34v6.34z"/>
-</vector>
+</vector>
\ No newline at end of file
index 4d844be1b95ec90d3d538160e11809353ff64b3e..bc365d6baec32002bdd1b228a1a3ed7dc8352fd2 100644 (file)
   along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>. -->
 
 <!-- android:fitsSystemWindows="true" moves the AppBar below the status bar.
-  When it is specified the theme should include <item name="android:windowTranslucentStatus">true</item>
-  to make the status bar a transparent, darkened overlay. -->
+    When it is specified the theme should include <item name="android:windowTranslucentStatus">true</item> to make the status bar a transparent, darkened overlay. -->
 <androidx.coordinatorlayout.widget.CoordinatorLayout
     android:id="@+id/about_coordinatorlayout"
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:android.support.design="http://schemas.android.com/apk/res-auto"
     android:layout_height="match_parent"
     android:layout_width="match_parent"
-    android:fitsSystemWindows="true" >
+    android:fitsSystemWindows="true">
 
-    <!-- The `LinearLayout` with `orientation="vertical"` moves the `ViewPager` below the `AppBarLayout`. -->
+    <!-- The linear layout with `orientation="vertical"` moves the view pager below the app bar layout. -->
     <LinearLayout
         android:layout_height="match_parent"
         android:layout_width="match_parent"
         android:orientation="vertical" >
 
-        <!-- We need to set `android:background="?attr/colorPrimaryDark"` here or any space to the right of the `TabLayout` on large devices will be the theme background color. -->
+        <!-- `android:background="?attr/colorPrimaryDark"` must be set here or any space to the right of the tab layout on large devices will be the theme background color. -->
         <com.google.android.material.appbar.AppBarLayout
             android:id="@+id/about_appbarlayout"
             android:layout_height="wrap_content"
@@ -52,7 +52,6 @@
             <!-- For some reason `tabIndicatorColor` does not pull from the style unless specified explicitly here. -->
             <com.google.android.material.tabs.TabLayout
                 android:id="@+id/about_tablayout"
-                xmlns:android.support.design="http://schemas.android.com/apk/res-auto"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
                 android.support.design:tabMode="scrollable"
diff --git a/app/src/main/res/layout/custom_tab_view.xml b/app/src/main/res/layout/custom_tab_view.xml
new file mode 100644 (file)
index 0000000..c23a23e
--- /dev/null
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright © 2015-2017,2019 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/>. -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_height="match_parent"
+    android:layout_width="wrap_content"
+    android:orientation="horizontal"
+    tools:ignore="UseCompoundDrawables" >
+
+    <ImageView
+        android:id="@+id/favorite_icon_imageview"
+        android:src="@drawable/world"
+        android:layout_height="26dp"
+        android:layout_width="26dp"
+        android:layout_gravity="center_vertical"
+        android:layout_marginEnd="5dp"
+        android:contentDescription="@string/favorite_icon" />
+
+    <TextView
+        android:id="@+id/title_textview"
+        android:layout_height="match_parent"
+        android:layout_width="wrap_content"
+        android:gravity="center_vertical"
+        android:textSize="16sp"
+        android:text="@string/new_tab"
+        app:autoSizeTextType="uniform"
+        android:maxWidth="150dp"
+        android:maxLines="2"
+        android:ellipsize="end" />
+</LinearLayout>
\ No newline at end of file
index 9642decb6ab0fdc364ae7f35507055ed27a430ae..b04c6bc86641f9de7c42d567a3a1c404a0cac63c 100644 (file)
                     android:layout_width="match_parent"
                     android:theme="@style/PrivacyBrowserAppBarLight" >
 
-                    <!-- The frame layout allows the toolbar, progress bar, and find on page bar to occupy the same space.  The scroll flags should be set on the immediate child of AppBarLayout. -->
-                    <FrameLayout
+                    <androidx.appcompat.widget.Toolbar
+                        android:id="@+id/toolbar"
                         android:layout_height="wrap_content"
                         android:layout_width="match_parent"
-                        app:layout_scrollFlags="scroll|enterAlways|snap" >
-
-                        <androidx.appcompat.widget.Toolbar
-                            android:id="@+id/toolbar"
-                            android:layout_height="wrap_content"
-                            android:layout_width="match_parent" />
+                        app:layout_scrollFlags="scroll|enterAlways|snap" />
 
-                        <!-- `android:max` changes the maximum `ProgressBar` value from 10000 to 100 to match progress percentage.
-                            `android:layout_height="2dp"` works best for API >= 23, but `3dp` is required for visibility on API <= 22.
-                            `tools:ignore="UnusedAttribute"` removes the lint warning about `progressTint` and `progressBackgroundTint` not applying to API < 21. -->
-                        <ProgressBar
-                            android:id="@+id/progress_bar"
-                            style="?android:attr/progressBarStyleHorizontal"
-                            android:layout_height="3dp"
-                            android:layout_width="match_parent"
-                            android:layout_gravity="bottom"
-                            android:max="100"
-                            android:progressTint="?attr/progressTintColor"
-                            android:progressBackgroundTint="@color/transparent"
-                            android:visibility="gone"
-                            tools:ignore="UnusedAttribute" />
+                    <HorizontalScrollView
+                        android:layout_height="wrap_content"
+                        android:layout_width="match_parent"
+                        app:layout_scrollFlags="scroll|enterAlways|snap" >
 
-                        <!-- The find on page linear layout.  It is initially `visibility="gone"`. -->
                         <LinearLayout
-                            android:id="@+id/find_on_page_linearlayout"
                             android:layout_height="wrap_content"
-                            android:layout_width="match_parent"
-                            android:orientation="horizontal"
-                            android:visibility="gone" >
+                            android:layout_width="wrap_content"
+                            android:orientation="horizontal" >
 
-                            <!-- `android:imeOptions="actionDone"` sets the keyboard to have a `check mark` key instead of a `new line` key. -->
-                            <EditText
-                                android:id="@+id/find_on_page_edittext"
-                                android:layout_height="wrap_content"
-                                android:layout_width="0dp"
-                                android:layout_weight="1"
-                                android:layout_marginStart="8dp"
-                                android:layout_marginEnd="4dp"
-                                android:hint="@string/find_on_page"
-                                android:lines="1"
-                                android:imeOptions="actionDone"
-                                android:inputType="text"
-                                tools:ignore="Autofill" />
-
-                            <TextView
-                                android:id="@+id/find_on_page_count_textview"
+                            <com.google.android.material.tabs.TabLayout
+                                android:id="@+id/tablayout"
                                 android:layout_height="wrap_content"
                                 android:layout_width="wrap_content"
-                                android:layout_marginStart="4dp"
-                                android:layout_marginEnd="4dp"
-                                android:text="@string/zero_of_zero" />
+                                app:tabMode="scrollable" />
 
                             <ImageView
-                                android:id="@+id/find_previous"
-                                android:src="@drawable/previous"
-                                android:layout_width="35dp"
-                                android:layout_height="35dp"
-                                android:layout_marginStart="4dp"
-                                android:layout_marginEnd="4dp"
+                                android:layout_height="wrap_content"
+                                android:layout_width="wrap_content"
                                 android:layout_gravity="center_vertical"
-                                android:tint="?attr/findOnPageIconTintColor"
-                                android:contentDescription="@string/previous"
-                                android:onClick="findPreviousOnPage" />
+                                android:paddingStart="15dp"
+                                android:paddingEnd="15dp"
+                                android:src="@drawable/add_light"
+                                android:tint="@color/gray_700"
+                                android:onClick="addTab"
+                                android:contentDescription="@string/add_tab" />
+                        </LinearLayout>
+                    </HorizontalScrollView>
 
-                            <ImageView
-                                android:id="@+id/find_next"
-                                android:src="@drawable/next"
-                                android:layout_width="35dp"
-                                android:layout_height="35dp"
-                                android:layout_marginStart="4dp"
-                                android:layout_marginEnd="4dp"
-                                android:layout_gravity="center_vertical"
-                                android:tint="?attr/findOnPageIconTintColor"
-                                android:contentDescription="@string/next"
-                                android:onClick="findNextOnPage" />
+                    <!-- The find on page linear layout.  It is initially `visibility="gone"`. -->
+                    <LinearLayout
+                        android:id="@+id/find_on_page_linearlayout"
+                        android:layout_height="wrap_content"
+                        android:layout_width="match_parent"
+                        android:orientation="horizontal"
+                        android:visibility="gone" >
 
-                            <ImageView
-                                android:id="@+id/close_find"
-                                android:src="@drawable/close_light"
-                                android:layout_width="35dp"
-                                android:layout_height="35dp"
-                                android:layout_marginStart="4dp"
-                                android:layout_marginEnd="8dp"
-                                android:layout_gravity="center_vertical"
-                                android:tint="?attr/findOnPageIconTintColor"
-                                android:contentDescription="@string/close"
-                                android:onClick="closeFindOnPage" />
-                        </LinearLayout>
-                    </FrameLayout>
+                        <!-- `android:imeOptions="actionDone"` sets the keyboard to have a `check mark` key instead of a `new line` key. -->
+                        <EditText
+                            android:id="@+id/find_on_page_edittext"
+                            android:layout_height="wrap_content"
+                            android:layout_width="0dp"
+                            android:layout_weight="1"
+                            android:layout_marginStart="8dp"
+                            android:layout_marginEnd="4dp"
+                            android:hint="@string/find_on_page"
+                            android:lines="1"
+                            android:imeOptions="actionDone"
+                            android:inputType="text"
+                            tools:ignore="Autofill" />
+
+                        <TextView
+                            android:id="@+id/find_on_page_count_textview"
+                            android:layout_height="wrap_content"
+                            android:layout_width="wrap_content"
+                            android:layout_marginStart="4dp"
+                            android:layout_marginEnd="4dp"
+                            android:text="@string/zero_of_zero" />
+
+                        <ImageView
+                            android:id="@+id/find_previous"
+                            android:src="@drawable/previous"
+                            android:layout_width="35dp"
+                            android:layout_height="35dp"
+                            android:layout_marginStart="4dp"
+                            android:layout_marginEnd="4dp"
+                            android:layout_gravity="center_vertical"
+                            android:tint="?attr/findOnPageIconTintColor"
+                            android:contentDescription="@string/previous"
+                            android:onClick="findPreviousOnPage" />
+
+                        <ImageView
+                            android:id="@+id/find_next"
+                            android:src="@drawable/next"
+                            android:layout_width="35dp"
+                            android:layout_height="35dp"
+                            android:layout_marginStart="4dp"
+                            android:layout_marginEnd="4dp"
+                            android:layout_gravity="center_vertical"
+                            android:tint="?attr/findOnPageIconTintColor"
+                            android:contentDescription="@string/next"
+                            android:onClick="findNextOnPage" />
+
+                        <ImageView
+                            android:id="@+id/close_find"
+                            android:src="@drawable/close_light"
+                            android:layout_width="35dp"
+                            android:layout_height="35dp"
+                            android:layout_marginStart="4dp"
+                            android:layout_marginEnd="8dp"
+                            android:layout_gravity="center_vertical"
+                            android:tint="?attr/findOnPageIconTintColor"
+                            android:contentDescription="@string/close"
+                            android:onClick="closeFindOnPage" />
+                    </LinearLayout>
                 </com.google.android.material.appbar.AppBarLayout>
 
                 <!-- `app:layout_behavior="@string/appbar_scrolling_view_behavior"` must be set on the sibling of AppBarLayout. -->
                     android:layout_height="match_parent"
                     app:layout_behavior="@string/appbar_scrolling_view_behavior" >
 
-                    <com.stoutner.privacybrowser.views.NestedScrollWebView
-                        android:id="@+id/main_webview"
-                        android:layout_height="match_parent"
+                    <!-- The frame layout allows the progress bar to float above the WebView. -->
+                    <FrameLayout
                         android:layout_width="match_parent"
-                        android:focusable="true"
-                        android:focusableInTouchMode="true" />
+                        android:layout_height="match_parent">
+
+                        <androidx.viewpager.widget.ViewPager
+                            android:id="@+id/webviewpager"
+                            android:layout_width="match_parent"
+                            android:layout_height="match_parent" />
+
+                        <!-- `android:max` changes the maximum `ProgressBar` value from 10000 to 100 to match progress percentage.
+                            `android:layout_height="2dp"` works best for API >= 23, but `3dp` is required for visibility on API <= 22.
+                            `tools:ignore="UnusedAttribute"` removes the lint warning about `progressTint` and `progressBackgroundTint` not applying to API < 21. -->
+                        <ProgressBar
+                            android:id="@+id/progress_bar"
+                            style="?android:attr/progressBarStyleHorizontal"
+                            android:layout_height="3dp"
+                            android:layout_width="match_parent"
+                            android:max="100"
+                            android:progressTint="?attr/progressTintColor"
+                            android:progressBackgroundTint="@color/transparent"
+                            android:visibility="gone"
+                            tools:ignore="UnusedAttribute" />
+                    </FrameLayout>
                 </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
             </androidx.coordinatorlayout.widget.CoordinatorLayout>
         </RelativeLayout>
diff --git a/app/src/main/res/layout/nestedscroll_webview.xml b/app/src/main/res/layout/nestedscroll_webview.xml
new file mode 100644 (file)
index 0000000..3ece03d
--- /dev/null
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright © 2019 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/>. -->
+
+<com.stoutner.privacybrowser.views.NestedScrollWebView
+    android:id="@+id/nestedscroll_webview"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="match_parent"
+    android:layout_width="match_parent"
+    android:focusable="true"
+    android:focusableInTouchMode="true" />
\ No newline at end of file
index ef93af167fcad6ad8931b50e8208329436533c82..48161524661d88e05b8581bfea4473fa20987968 100644 (file)
   You should have received a copy of the GNU General Public License
   along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>. -->
 
-<!-- `RelativeLayout` is used instead of a `LinearLayout` because `supportAppBar` does not let `android:layout_weight="1"` cause `urlTextBox` to fill all the available space. -->
-<RelativeLayout
-    android:id="@+id/url_app_bar_relativelayout"
+<!-- `android:imeOptions="actionGo"` sets the keyboard to have a go key instead of a new line key.  `android:inputType="textUri"` disables spell check in the `EditText`.
+     `android:autofillHints` only applies to API >= 26.  `tools:ignore="UnusedAttribute"` removes the lint warning. -->
+<EditText
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/url_edittext"
     android:layout_height="wrap_content"
     android:layout_width="match_parent"
-    tools:context=".activities.MainWebViewActivity" >
-
-    <!-- Set `@drawable/world` as the initial as the initial favorite icon.  `layout_height` and `layout_width` of 26dp matches the `AppBar` icons. -->
-    <ImageView
-        android:id="@+id/favorite_icon"
-        android:src="@drawable/world"
-        android:layout_height="26dp"
-        android:layout_width="26dp"
-        android:layout_centerVertical="true"
-        android:layout_marginStart="4dp"
-        android:onClick="viewSslCertificate"
-        android:contentDescription="@string/favorite_icon" />
-
-    <!-- `android:imeOptions="actionGo"` sets the keyboard to have a go key instead of a new line key.  `android:inputType="textUri"` disables spell check in the `EditText`.
-     `android:autofillHints` only applies to API >= 26.  `tools:ignore="UnusedAttribute"` removes the lint warning. -->
-    <EditText
-        android:id="@+id/url_edittext"
-        android:layout_height="wrap_content"
-        android:layout_width="match_parent"
-        android:layout_toEndOf="@id/favorite_icon"
-        android:hint="@string/url_or_search_terms"
-        android:imeOptions="actionGo"
-        android:inputType="textUri"
-        android:selectAllOnFocus="true"
-        android:autofillHints=""
-        tools:ignore="UnusedAttribute" />
-</RelativeLayout>
\ No newline at end of file
+    android:hint="@string/url_or_search_terms"
+    android:imeOptions="actionGo"
+    android:inputType="textUri"
+    android:selectAllOnFocus="true"
+    android:autofillHints=""
+    tools:ignore="UnusedAttribute" />
\ No newline at end of file
index 74a9249ce10361b91d80e675385e3e94bf8f218f..6181453cfc234e1113943294547dcfaff77b26eb 100644 (file)
 
 <menu
     xmlns:android="http://schemas.android.com/apk/res/android">
+
     <item
-        android:id="@+id/home"
-        android:title="@string/home"
-        android:icon="@drawable/home_enabled_light"
+        android:id="@+id/close_tab"
+        android:title="@string/close_tab"
+        android:icon="@drawable/tab"
         android:orderInCategory="10" />
 
     <item
-        android:id="@+id/back"
-        android:title="@string/back"
-        android:icon="@drawable/back"
+        android:id="@+id/clear_and_exit"
+        android:title="@string/clear_and_exit"
+        android:icon="@drawable/open_with_external_app_enabled_light"
         android:orderInCategory="20" />
 
-    <item
-        android:id="@+id/forward"
-        android:title="@string/forward"
-        android:icon="@drawable/forward"
-        android:orderInCategory="30" />
+    <!-- If a group has an id, a line is drawn above it in the navigation view. -->
+    <group
+        android:id="@+id/navigation_group" >
 
-    <item
-        android:id="@+id/history"
-        android:title="@string/history"
-        android:icon="@drawable/history"
-        android:orderInCategory="40" />
+        <item
+            android:id="@+id/home"
+            android:title="@string/home"
+            android:icon="@drawable/home_enabled_light"
+            android:orderInCategory="30" />
+
+        <item
+            android:id="@+id/back"
+            android:title="@string/back"
+            android:icon="@drawable/back"
+            android:orderInCategory="40" />
+
+        <item
+            android:id="@+id/forward"
+            android:title="@string/forward"
+            android:icon="@drawable/forward"
+            android:orderInCategory="50" />
+
+        <item
+            android:id="@+id/history"
+            android:title="@string/history"
+            android:icon="@drawable/history"
+            android:orderInCategory="60" />
+    </group>
 
     <!-- If a group has an id, a line is drawn above it in the navigation view. -->
     <group
         android:id="@+id/features_group" >
+
         <item
             android:id="@+id/requests"
             android:title="@string/requests"
             android:icon="@drawable/block_ads_enabled_light"
-            android:orderInCategory="50" />
+            android:orderInCategory="70" />
 
         <item
             android:id="@+id/downloads"
             android:title="@string/downloads"
             android:icon="@drawable/downloads_light"
-            android:orderInCategory="60" />
+            android:orderInCategory="80" />
     </group>
 
     <!-- If a group has an id, a line is drawn above it in the navigation view. -->
     <group
         android:id="@+id/settings_group" >
+
         <item
             android:id="@+id/domains"
             android:title="@string/domains"
             android:icon="@drawable/domains"
-            android:orderInCategory="70" />
+            android:orderInCategory="90" />
 
         <item
             android:id="@+id/settings"
             android:title="@string/settings"
             android:icon="@drawable/settings"
-            android:orderInCategory="80" />
+            android:orderInCategory="100" />
 
         <item
             android:id="@+id/import_export"
             android:title="@string/import_export"
             android:icon="@drawable/import_export_light"
-            android:orderInCategory="90" />
+            android:orderInCategory="110" />
 
         <item
             android:id="@+id/logcat"
             android:title="@string/logcat"
             android:icon="@drawable/bug"
-            android:orderInCategory="100" />
+            android:orderInCategory="120" />
     </group>
 
     <!-- If a group has an id, a line is drawn above it in the navigation view. -->
     <group
         android:id="@+id/info_group" >
+
         <item
             android:id="@+id/guide"
             android:title="@string/guide"
             android:icon="@drawable/guide"
-            android:orderInCategory="110" />
+            android:orderInCategory="130" />
 
         <item
             android:id="@+id/about"
             android:title="@string/about"
             android:icon="@drawable/about_light"
-            android:orderInCategory="120" />
-    </group>
-
-    <!-- If a group has an id, a line is drawn above it in the navigation view. -->
-    <group
-        android:id="@+id/exit_group" >
-        <item
-            android:id="@+id/clear_and_exit"
-            android:title="@string/clear_and_exit"
-            android:icon="@drawable/open_with_external_app_enabled_light"
-            android:orderInCategory="130" />
+            android:orderInCategory="140" />
     </group>
 </menu>
\ No newline at end of file
index 7773b01d6e565f8c71a5dceb3f8dcad932414030..992dc40d321b44812050ea38e2bf7bfb9b30d9b4 100644 (file)
     <string name="username">Benutzername</string>
     <string name="password">Passwort</string>
 
-    <!-- MainWebViewActivity Navigation Drawer. -->
+    <!-- MainWebViewActivity Navigation Menu. -->
     <string name="navigation_drawer">Navigationspanel</string>
     <string name="navigation">Navigation</string>
+    <string name="clear_and_exit">Leeren und verlassen</string>
     <string name="home">Startseite</string>
     <string name="back">Zurück</string>
     <string name="forward">Vorwärts</string>
     <string name="logcat">Logcat</string>
     <string name="guide">Handbuch</string>
     <string name="about">Infos</string>
-    <string name="clear_and_exit">Leeren und verlassen</string>
 
     <!-- MainWebViewActivity Options Menu. -->
     <string name="javascript">JavaScript</string>
index 853210ccaa5aace2ae424692588a9bd60d001550..564bbdf4e35583f88b87431cdb34ab9c2fe21aba 100644 (file)
     <string name="username">Usuario</string>
     <string name="password">Contraseña</string>
 
-    <!-- MainWebViewActivity Navigation Drawer. -->
+    <!-- MainWebViewActivity Navigation Menu. -->
     <string name="navigation_drawer">Caja de navegación</string>
     <string name="navigation">Navegación</string>
+    <string name="clear_and_exit">Borrar y salir</string>
     <string name="home">Inicio</string>
     <string name="back">Atrás</string>
     <string name="forward">Adelante</string>
     <string name="logcat">Logcat</string>
     <string name="guide">Guía</string>
     <string name="about">Acerca de</string>
-    <string name="clear_and_exit">Borrar y salir</string>
 
     <!-- MainWebViewActivity Options Menu. -->
     <string name="javascript">Javascript</string>
index 60ca51557cce3fdd0ba56aadc4a80742385d3233..168a3ed9414ca70c277e590361a64cddf4f30096 100644 (file)
     <string name="username">Nome utente</string>
     <string name="password">Password</string>
 
-    <!-- MainWebViewActivity Navigation Drawer. -->
+    <!-- MainWebViewActivity Navigation Menu. -->
     <string name="navigation_drawer">Menù di navigazione</string>
     <string name="navigation">Navigazione</string>
+    <string name="clear_and_exit">Elimina dati ed esci</string>
     <string name="home">Home</string>
     <string name="back">Indietro</string>
     <string name="forward">Avanti</string>
     <string name="logcat">Logcat</string>
     <string name="guide">Guida</string>
     <string name="about">Informazioni</string>
-    <string name="clear_and_exit">Elimina dati ed esci</string>
 
     <!-- MainWebViewActivity Options Menu. -->
     <string name="javascript">JavaScript</string>
index 486b65c1f7cb99396e17b89989e122702846a65d..9ff1ed06a88b5b7a839645655a3322c23ae2fdab 100644 (file)
     <string name="username">Имя пользователя</string>
     <string name="password">Пароль</string>
 
-    <!-- MainWebViewActivity Navigation Drawer. -->
+    <!-- MainWebViewActivity Navigation Menu. -->
     <string name="navigation_drawer">Навигационная панель</string>
     <string name="navigation">Навигация</string>
+    <string name="clear_and_exit">Очистить и выйти</string>
     <string name="home">Домой</string>
     <string name="back">Назад</string>
     <string name="forward">Вперед</string>
     <string name="logcat">Logcat</string>
     <string name="guide">Руководство</string>
     <string name="about">О Privacy Browser</string>
-    <string name="clear_and_exit">Очистить и выйти</string>
 
     <!-- MainWebViewActivity Options Menu. -->
     <string name="javascript">JavaScript</string>
index 8720f64d331f353db03a7d8e07076fc4cdd96bcb..e6dfa98ca358067b666399f683286e648aad0165 100644 (file)
     <string name="username">Kullanıcı Adı</string>
     <string name="password">Şifre</string>
 
-    <!-- MainWebViewActivity Navigation Drawer. -->
+    <!-- MainWebViewActivity Navigation Menu. -->
     <string name="navigation_drawer">Açılır Menü</string>
     <string name="navigation">Gezinti</string>
+    <string name="clear_and_exit">Temizle ve Çık</string>
     <string name="home">Anasayfa</string>
     <string name="back">Geri</string>
     <string name="forward">İleri</string>
     <string name="logcat">Logcat</string>
     <string name="guide">Rehber</string>
     <string name="about">Hakkında</string>
-    <string name="clear_and_exit">Temizle ve Çık</string>
 
     <!-- MainWebViewActivity Options Menu. -->
     <string name="javascript">JavaScript</string>
index 23d45435c3aca9b74700e4dcce65fcdeaed4cfbd..0013e8e66674d7c2bb5a67474438d5e3a9a77c7b 100644 (file)
@@ -51,6 +51,8 @@
     <string name="no_title">No title</string>
     <string name="unrecognized_url">Unrecognized URL:</string>
     <string name="open_with">Open with</string>
+    <string name="new_tab">New tab</string>
+    <string name="add_tab">Add tab</string>
 
     <!-- Save As. -->
     <string name="save_as">Save As</string>
     <string name="username">Username</string>
     <string name="password">Password</string>
 
-    <!-- MainWebViewActivity Navigation Drawer. -->
+    <!-- MainWebViewActivity Navigation Menu. -->
     <string name="navigation_drawer">Navigation Drawer</string>
     <string name="navigation">Navigation</string>
+    <string name="close_tab">Close Tab</string>
+    <string name="clear_and_exit">Clear and Exit</string>
     <string name="home">Home</string>
     <string name="back">Back</string>
     <string name="forward">Forward</string>
     <string name="logcat">Logcat</string>
     <string name="guide">Guide</string>
     <string name="about">About</string>
-    <string name="clear_and_exit">Clear and Exit</string>
 
     <!-- MainWebViewActivity Options Menu. -->
     <string name="javascript">JavaScript</string>
diff --git a/fastlane/metadata/android/de-DE/full_description.txt b/fastlane/metadata/android/de-DE/full_description.txt
new file mode 100644 (file)
index 0000000..e1fefd2
--- /dev/null
@@ -0,0 +1,13 @@
+Die meisten Browser plaudern still und heimlich jede Menge Informationen an Websiten aus, welche genutzt werden können, um deren Benutzer zu verfolgen und deren Privatsphäre zu kompromittieren. Webseiten und Werbenetzwerke nutzen Technologien wie JavaScript, Cookies, DOM-Speicher, User-Agents und andere, um jeden einzelnen Browser-Benutzer eindeutig zu identifizieren und ihn zwischen seinen Besuchen und sogar quer durch das Internet zu verfolgen.
+
+Privacy Browser wird entwickelt, um die Menge an Informationen, die an Websites geliefert wird, zu minimieren. In der Voreinstellung sind Funktionen, die Ihre Privatsphäre beeinträchtigen können, grundsätzlich deaktiviert. Wenn Webseiten jedoch spezielle Funktionen benötigen, um zu funktionieren, können diese beim Besuch der betreffenden Seite bewusst einmalig aktiviert werden. Darüber hinaus können spezielle Einstellungen für bestimmte Domains auch abgespeichert werden, damit sie bei jedem Besuch der betreffenden Seite automatisch aktiviert und danach wieder deaktiviert werden.
+
+Privacy Browser nutzt aktuell Android's eingebaute WebView-Komponente, um Webseiten anzuzeigen. Er funktioniert daher am Besten, wenn die letzte Version von WebView installiert ist (siehe https://www.stoutner.com/privacy-browser/common-settings/webview/). Ab Version 4.x wird Privacy Browser eine abgeleitete Version von Android's WebView - Privacy WebView genannt - verwenden, um erweiterte Privatsphäre-Funktionen zu bieten.
+
+Achtung: Android KitKat (Version 4.4.x, API 19) wurde mit einer alten Version von OpenSSL ausgeliefert, welche für Man-In-The-Middle-Angriffe (MITM) anfällig ist, wenn auf Webseiten mit veralteten Protokollen und Cipher-Suites gesurft wird. Weitere Informationen dazu sind auf https://www.stoutner.com/kitkat-security-problems/ verfügbar.
+
+Features:
+• Integrierter EasyList-Werbeblocker
+• TOR-Proxy-Unterstützung mittels Orbot
+• Verankerung von SSL-Zertifikaten (Pinning)
+• Import/Export von Einstellungen und Lesezeichen
\ No newline at end of file
diff --git a/fastlane/metadata/android/de-DE/short_description.txt b/fastlane/metadata/android/de-DE/short_description.txt
new file mode 100644 (file)
index 0000000..cb504a5
--- /dev/null
@@ -0,0 +1 @@
+Ein Browser, der Ihre Privatsphäre schützt.
\ No newline at end of file
diff --git a/fastlane/metadata/android/de-DE/title.txt b/fastlane/metadata/android/de-DE/title.txt
new file mode 100644 (file)
index 0000000..ca458fe
--- /dev/null
@@ -0,0 +1 @@
+Privacy Browser
\ No newline at end of file
index ab03d23bd5639d2be9653c8b066772a7807238a9..9992b757224c9b6d3f25549c7e78cc7ff49ce21f 100644 (file)
@@ -1,13 +1,13 @@
-Privacy Browser protects your privacy by disabling features like JavaScript, DOM storage, and cookies that are used by websites to track users.  Settings can be adjusted by domain and on-the-fly to enable these features when needed.  In addition, Privacy Browser incorporates the EasyList block lists, which block many tracking technologies even when JavaScript is enabled.
+Most browsers silently give websites massive amounts of information that allows them to track you and compromise your privacy. Websites and ad networks use technologies like JavaScript, cookies, DOM storage, user agents, and many other things to uniquely identify each user and track them between visits and across the web.
 
-Privacy Browser does not have tabbed browsing yet.  This is a planned feature for the 3.x series.
+Privacy Browser is designed to minimize the amount of information the browser presents to websites. By default, privacy sensitive features are disabled. If one of these technologies is required for a website to function correctly, the user may choose to turn it on for just that visit. Or, they can use domain settings to automatically turn on certain features when entering a specific website and turn them off again when leaving.
 
-Privacy Browser currently uses Android’s built-in WebView to render web pages.  As such, it works best when the latest version of WebView is installed.  In the 4.x series, Privacy Browser will switch to a forked version of Android’s WebView called Privacy WebView that will allow for advanced privacy features.
+Privacy Browser currently uses Android’s built-in WebView to render web pages. As such, it works best when the latest version of WebView is installed (see https://www.stoutner.com/privacy-browser/common-settings/webview/). In the 4.x series, Privacy Browser will switch to a forked version of Android’s WebView called Privacy WebView that will allow for advanced privacy features.
 
-Warning: Due to limitations in the OS, Privacy Browser is susceptible to MITM (Man In The Middle) attacks when browsing insecure websites from devices running Android KitKat (version 4.4.x, API 19).  More information about this issue is available at https://www.stoutner.com/kitkat-security-problems/.
+Warning: Android KitKat (version 4.4.x, API 19) ships an older version of OpenSSL, which is susceptible to MITM (Man In The Middle) attacks when browsing websites that use outdated protocols and cipher suites. More information about this issue is available at https://www.stoutner.com/kitkat-security-problems/.
 
 Features:
+• Integrated EasyList ad blocking.
 • Tor Orbot proxy support.
 • SSL certificate pinning.
-• Full screen browsing mode.
-• Night mode.
+• Import/Export of settings and bookmarks.
\ No newline at end of file
index 8ad80090d9845e6ecd208bd38d9bd61d87be1303..e7f6b8991e5e994f08d7e8043ef4ce2b934aea7c 100644 (file)
@@ -1 +1 @@
-A web browser that respects your privacy.
+A web browser that respects your privacy.
\ No newline at end of file
index beb603058881b97994014e08c1c78f73d18262b6..ca458fec1818d79f247a3135e0fb097a9c1ce0c1 100644 (file)
@@ -1 +1 @@
-Privacy Browser
+Privacy Browser
\ No newline at end of file
diff --git a/fastlane/metadata/android/tr-TR/full_description.txt b/fastlane/metadata/android/tr-TR/full_description.txt
new file mode 100644 (file)
index 0000000..1a6d102
--- /dev/null
@@ -0,0 +1,13 @@
+Çoğu tarayıcı, sizi takip etmelerini sağlayacak ve gizliliğinizi riske edecek büyük miktarda bilgiyi sessizce web sitelerine verir. Web siteleri ve reklam ağları, her kullanıcıyı benzersiz şekilde tanımlamak ve web genelinde ve ziyaretleri arasında hepsini takip etmek için JavaScript, çerezler, DOM depolama alanı, kullanıcı aracıları ve daha birçok farklı teknolojileri kullanır.
+
+Privacy Browser, bir tarayıcının web sitelerine verebileceği bilgi miktarını en aza indirecek şekilde tasarlanmıştır. Gizliliğe duyarlı özellikler varsayılan olarak devre dışıdır. Bir web sitesinin düzgün çalışması için bu teknolojilerden biri gerekliyse, kullanıcı bu ziyaret için o özelliği etkinleştirmeyi seçebilir. Veya, belirli bir web sitesine girerken belirli özellikleri otomatik olarak açmak ve çıkarken tekrar kapatmak için domain ayarlarını kullanabilir.
+
+Privacy Browser, web sayfalarını oluşturmak için şu anda Android'in yerleşik Web Görünümü'nü kullanıyor. Bu nedenle, en güncel Web Görünümü sürümü kurulu olduğunda en iyi şekilde çalışır (bakınız https://www.stoutner.com/privacy-browser/common-settings/webview/). 4.x serisinde, Privacy Browser, gelişmiş gizlilik özelliklerine izin verecek, Android Web Görünümü'nün bölünmüş bir sürümü olan Privacy Web Görünümü'ne geçecektir.
+
+Uyarı: Android KitKat (sürüm 4.4.x, API 19), güncel olmayan protokolleri ve şifre takımlarını kullanan web sitelerine göz atarken MITM (Ortadaki Adam) saldırılarına açık olan eski bir OpenSSL sürümünü içerir. Bu konuda daha fazla bilgi için https://www.stoutner.com/kitkat-security-problems/.
+
+Özellikler:
+• Entegre EasyList reklam engelleme.
+• Tor Orbot proxy desteği.
+• SSL sertifikası sabitlemesi.
+• Ayarların ve yer imlerinin içe / dışa aktarımı.
\ No newline at end of file
diff --git a/fastlane/metadata/android/tr-TR/short_description.txt b/fastlane/metadata/android/tr-TR/short_description.txt
new file mode 100644 (file)
index 0000000..8845d41
--- /dev/null
@@ -0,0 +1 @@
+Mahremiyetinize saygı duyan bir web tarayıcısı.
\ No newline at end of file
diff --git a/fastlane/metadata/android/tr-TR/title.txt b/fastlane/metadata/android/tr-TR/title.txt
new file mode 100644 (file)
index 0000000..ca458fe
--- /dev/null
@@ -0,0 +1 @@
+Privacy Browser
\ No newline at end of file