]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/commitdiff
Display the file name under the URI. https://redmine.stoutner.com/issues/1159
authorSoren Stoutner <soren@stoutner.com>
Thu, 20 Aug 2026 19:49:20 +0000 (12:49 -0700)
committerSoren Stoutner <soren@stoutner.com>
Thu, 20 Aug 2026 19:49:20 +0000 (12:49 -0700)
27 files changed:
app/build.gradle
app/src/main/assets/de/about_permissions.html
app/src/main/assets/de/guide_requests.html
app/src/main/assets/en/guide_requests.html
app/src/main/assets/es/about_permissions.html
app/src/main/assets/es/guide_requests.html
app/src/main/assets/fr/guide_requests.html
app/src/main/assets/it/about_permissions.html
app/src/main/assets/it/guide_requests.html
app/src/main/assets/pt-rBR/guide_requests.html
app/src/main/assets/ru/about_permissions.html
app/src/main/assets/ru/guide_requests.html
app/src/main/assets/tr/guide_requests.html
app/src/main/assets/zh-rCN/guide_requests.html
app/src/main/java/com/stoutner/privacybrowser/activities/ImportExportActivity.kt
app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.kt
app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkDialog.kt
app/src/main/java/com/stoutner/privacybrowser/dialogs/OpenDialog.kt
app/src/main/res/layout/import_export_bottom_appbar.xml
app/src/main/res/layout/import_export_top_appbar.xml
app/src/main/res/layout/move_to_folder_dialog.xml
app/src/main/res/layout/move_to_folder_item_linearlayout.xml
app/src/main/res/layout/open_dialog.xml
app/src/main/res/layout/pinned_mismatch_linearlayout.xml
app/src/main/res/layout/pinned_mismatch_tab_linearlayout.xml
app/src/main/res/layout/save_dialog.xml
build.gradle

index 7686b53ba7564cba039c2f133798a0a38893db28..c9f75cc4e3c8f133b8a335f8333c06bd237414a0 100644 (file)
@@ -81,7 +81,7 @@ dependencies {
     implementation "androidx.activity:activity-ktx:1.13.0"
     implementation 'androidx.arch.core:core-common:2.2.0'
     implementation 'androidx.arch.core:core-runtime:2.2.0'
-    implementation 'androidx.appcompat:appcompat:1.7.1'
+    implementation 'androidx.appcompat:appcompat:1.8.0'
     implementation 'androidx.cardview:cardview:1.0.0'
     implementation 'androidx.coordinatorlayout:coordinatorlayout:1.3.0'
     implementation 'androidx.core:core-ktx:1.19.0'
@@ -90,7 +90,7 @@ dependencies {
     implementation 'androidx.preference:preference-ktx:1.2.1'
     implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0'
     implementation 'androidx.viewpager:viewpager:1.1.0'
-    implementation 'androidx.webkit:webkit:1.16.0'
+    implementation 'androidx.webkit:webkit:1.17.0'
 
     // Include the Kotlin standard library.  This should be the same version number listed in project build.gradle.
     implementation 'org.jetbrains.kotlin:kotlin-stdlib:2.2.10'
index b20c81f235be8cc2698086e34fc191439d4a0156..45eb02d451b49bb4f47d147e49683e6aec8f392e 100644 (file)
@@ -2,9 +2,9 @@
   SPDX-License-Identifier: GPL-3.0-or-later
   SPDX-FileCopyrightText: 2016-2018, 2020-2022, 2025-2026 Soren Stoutner <soren@stoutner.com>
 
-  Translation 2016 Aaron Gerlach <aaron@gerlach.com>.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+  Translation 2019, 2022, 2026 Bernhard G. Keller.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
   Translation 2018 Stefan Erhardt.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
-  Translation 2019, 2022 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 Android <https://www.stoutner.com/privacy-browser-android/>.
 
@@ -43,8 +43,8 @@
 
         <h3>Geräte in der Nähe</h3>
         <p><a href="https://developer.android.com/reference/android/Manifest.permission.html#ACCESS_LOCAL_NETWORK">android.permission.<wbr>ACCESS_LOCAL_NETWORK</a></p>
-        <p>This is a dangerous permission, meaning users must manually enable it if they desire the functionality.  It was added in API 37 (Android 17).
-            It is required to load web pages on the Local Area Network (LAN), like the web interface of a wireless router.</p>
+        <p>Dies ist eine gefährliche Berechtigung, sodass sie vom Benutzer manuell aktiviert werden muss, wenn sie benötigt wird.
+            Sie wurde mit API 37 (Android 17) eingeführt und wird benötigt, um Web-Seiten in einem lokalen Netzwerk (LAN) zu laden, wie z.B. das Web-Interface eines WLAN-Routers o.ä.</p>
 
         <h3><a href="https://developer.android.com/about/versions/13/features#runtime-receivers">
             com.stoutner.privacybrowser.<wbr>standard.<wbr>DYNAMIC_<wbr>RECEIVER_<wbr>NOT_<wbr>EXPORTED_<wbr>PERMISSION</a></h3>
index 51d079da717c50d0ee0e402d91bdc0222f90f67b..e5bb3a5e8798964651de72c7344d36a3f5822e6b 100644 (file)
             Eine detailiertere Beschreibung, wie die Filterlisten-Einträge verarbeitet werden, ist unter <a href="https://www.stoutner.com/privacy-browser-android/filter-lists/">stoutner.com</a> verfügbar.</p>
 
 
-        <h3>LAN Requests</h3>
+        <h3><svg class="header"><use href="../shared_images/local_activity.svg#icon"/></svg> Lokale Netzwerk-Anfragen</h3>
 
-        <p>Beginning with Android 17 (API 37), Android’s WebView cannot make requests to devices on the LAN (Local Area Network) unless the
-            <a href="https://developer.android.com/reference/android/Manifest.permission.html#ACCESS_LOCAL_NETWORK">Nearby Devices</a> permission has been granted.
-            This is a dangerous permission that must be explicitly enabled by the user.  Devices on the LAN include everything that is on the same subnet as Privacy Browser, like the web interface of a wireless router.
-            Privacy Browser does not prompt for this permission, and there is no need for users to enable it if they only want to browse the general internet.
-            Users who need to be able to load web pages from the LAN can grant this permission in the OS settings for Privacy Browser.</p>
+        <p>Seit Android 17 (API 37), kann Android’s WebView keine Anfragen an Geräte im LAN (Local Area Network) senden, sofern dafür nicht die Berechtigung
+            <a href="https://developer.android.com/reference/android/Manifest.permission.html#ACCESS_LOCAL_NETWORK">Geräte in der Nähe (Nearby Devices)</a> gewährt wurde.
+            Dies ist eine gefährliche Berechtigung, welche vom Benutzer explizit aktiviert werden muss.
+            Geräte im LAN beinhalten Alles im selben Subnet, in dem sich auch Privacy Browser befindet, wie z.B. das Web-Interface eines WLAN-Routers.
+            Privacy Browser fordert diese Berechtigung nicht ein und es ist nicht notwendig, sie zu aktivieren, um lediglich das allgemeinen Internet zu nutzen.
+            Benutzer, die Web-Seiten im LAN nutzen wollen, können diese Berechtigung in den Betriebssystem-Einstellungen von Privacy Browsergewähren.</p>
 
-        <img class="center224" src="images/nearby_devices.png" alt="Nearby Devices Permission"/>
+        <img class="center224" src="images/nearby_devices.png" alt="Berechtigung für Geräte in der Nähe"/>
     </body>
 </html>
index f6b34d7e693750561ab919c21373ed99f6d336c3..df4097ffe0eccb564023d2419c40125f261b4618 100644 (file)
@@ -73,7 +73,7 @@
             A more detailed description of how the filter list entries are processed is available at <a href="https://www.stoutner.com/privacy-browser-android/filter-lists/">stoutner.com</a>.</p>
 
 
-        <h3>LAN Requests</h3>
+        <h3><svg class="header"><use href="../shared_images/local_activity.svg#icon"/></svg> LAN Requests</h3>
 
         <p>Beginning with Android 17 (API 37), Android’s WebView cannot make requests to devices on the LAN (Local Area Network) unless the
             <a href="https://developer.android.com/reference/android/Manifest.permission.html#ACCESS_LOCAL_NETWORK">Nearby Devices</a> permission has been granted.
index b5efdb5cbea63a698692dfb3dc190c4a4368270d..02c7a015701a9a85271da9f8d186356c9657f4da 100644 (file)
@@ -2,7 +2,7 @@
   SPDX-License-Identifier: GPL-3.0-or-later
   SPDX-FileCopyrightText: 2016-2018, 2020-2022, 2025-2026 Soren Stoutner <soren@stoutner.com>
 
-  Translation 2017-2018, 2022 Jose A. León.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+  Translation 2017-2018, 2022, 2026 Jose A. León.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android/>.
 
@@ -41,8 +41,8 @@
 
         <h3>Dispositivos cercanos</h3>
         <p><a href="https://developer.android.com/reference/android/Manifest.permission.html#ACCESS_LOCAL_NETWORK">android.permission.<wbr>ACCESS_LOCAL_NETWORK</a></p>
-        <p>This is a dangerous permission, meaning users must manually enable it if they desire the functionality.  It was added in API 37 (Android 17).
-            It is required to load web pages on the Local Area Network (LAN), like the web interface of a wireless router.</p>
+        <p>Este es un permiso peligroso, lo que significa que los usuarios deben activarlo manualmente si desean esta funcionalidad. Fue añadido en la API 37 (Android 17).
+            Es necesario para cargar páginas web en la red de área local (LAN), como la interfaz web de un router inalámbrico.</p>
 
         <h3><a href="https://developer.android.com/about/versions/13/features#runtime-receivers">
             com.stoutner.privacybrowser.<wbr>standard.<wbr>DYNAMIC_<wbr>RECEIVER_<wbr>NOT_<wbr>EXPORTED_<wbr>PERMISSION</a></h3>
index 50cf59d3938ff9c173c274ee908b905820e825d8..8247014768086ff9aa5198bf379c27c1a98e6904 100644 (file)
             Encontrará una descripción más detallada de cómo se procesan las entradas de la lista de filtros en <a href="https://www.stoutner.com/privacy-browser-android/filter-lists/">stoutner.com</a>.</p>
 
 
-        <h3>LAN Requests</h3>
+        <h3><svg class="header"><use href="../shared_images/local_activity.svg#icon"/></svg> Solicitudes de LAN</h3>
 
-        <p>Beginning with Android 17 (API 37), Android’s WebView cannot make requests to devices on the LAN (Local Area Network) unless the
-            <a href="https://developer.android.com/reference/android/Manifest.permission.html#ACCESS_LOCAL_NETWORK">Nearby Devices</a> permission has been granted.
-            This is a dangerous permission that must be explicitly enabled by the user.  Devices on the LAN include everything that is on the same subnet as Privacy Browser, like the web interface of a wireless router.
-            Privacy Browser does not prompt for this permission, and there is no need for users to enable it if they only want to browse the general internet.
-            Users who need to be able to load web pages from the LAN can grant this permission in the OS settings for Privacy Browser.</p>
+        <p>A partir de Android 17 (API 37), WebView de Android no puede realizar solicitudes a dispositivos de la LAN (Red de Área Local) a menos que
+            <a href="https://developer.android.com/reference/android/Manifest.permission.html#ACCESS_LOCAL_NETWORK">el permiso Dispositivos Cercanos</a> se haya concedido.
+            Este es un permiso peligroso que debe ser habilitado explícitamente por el usuario.
+            Los dispositivos de la LAN incluyen todos aquellos que están en la misma subred que Navegador Privado, como la interfaz web de un router inalámbrico.
+            Navegador Privado no solicita este permiso, y no hay necesidad de que los usuarios lo activen si solo desean navegar por internet en general.
+            Los usuarios que necesiten cargar páginas webs desde la LAN pueden conceder este permiso en la configuración del sistema operativo para Navegador Privado.</p>
 
-        <img class="center224" src="images/nearby_devices.png" alt="Nearby Devices Permission"/>
+        <img class="center224" src="images/nearby_devices.png" alt="Permiso de Dispositivos Cercanos"/>
     </body>
 </html>
index f4e3117479d6e5f32c97581fe10655aaafd6e3ff..3846de168830018e82135c8bf78c5f066ed6006b 100644 (file)
@@ -78,7 +78,7 @@
             <a href="https://www.stoutner.com/privacy-browser-android/filter-lists/">stoutner.com</a>.</p>
 
 
-        <h3>LAN Requests</h3>
+        <h3><svg class="header"><use href="../shared_images/local_activity.svg#icon"/></svg> LAN Requests</h3>
 
         <p>Beginning with Android 17 (API 37), Android’s WebView cannot make requests to devices on the LAN (Local Area Network) unless the
             <a href="https://developer.android.com/reference/android/Manifest.permission.html#ACCESS_LOCAL_NETWORK">Nearby Devices</a> permission has been granted.
index 5aa290db0f22232f7e0dffbacf7235c9afc84032..4449eeeadb91d54c4759ce66a5c74ede9d78b3ad 100644 (file)
@@ -2,7 +2,7 @@
   SPDX-License-Identifier: GPL-3.0-or-later
   SPDX-FileCopyrightText: 2017-2018, 2020-2022, 2025-2026 Soren Stoutner <soren@stoutner.com>
 
-  Translation 2017-2018, 2022 Francesco Buratti.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
+  Translation 2017-2018, 2022, 2026 Francesco Buratti.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android/>.
 
@@ -42,8 +42,8 @@
 
         <h3>Dispositivi nelle vicinanze</h3>
         <p><a href="https://developer.android.com/reference/android/Manifest.permission.html#ACCESS_LOCAL_NETWORK">android.permission.<wbr>ACCESS_LOCAL_NETWORK</a></p>
-        <p>This is a dangerous permission, meaning users must manually enable it if they desire the functionality.  It was added in API 37 (Android 17).
-            It is required to load web pages on the Local Area Network (LAN), like the web interface of a wireless router.</p>
+        <p>Si tratta di una autorizzazione pericolosa, per cui gli utenti devono abilitarla manualmente nel caso vogliano utilizzare questa funzionalità.  E' stata aggiunta con la API 37 (Android 17).
+            E' richiesta per caricare pagine web nella Local Area Network (LAN), come l'interfaccia web di un router wireless.</p>
 
         <h3><a href="https://developer.android.com/about/versions/13/features#runtime-receivers">
             com.stoutner.privacybrowser.<wbr>standard.<wbr>AUTORIZZAZIONE_<wbr>DYNAMIC_<wbr>RECEIVER_<wbr>NON_<wbr>ESPORTATA</a></h3>
index f5e12f7da3d727484d19c09d781907fb6a9987ed..b32645388489f4a23e264e1155ec5b6d36c38799 100644 (file)
             Una descrizione più dettagliata di come le liste dei filtri sono processate è disponibile su <a href="https://www.stoutner.com/privacy-browser-android/filter-lists/">stoutner.com</a>.</p>
 
 
-        <h3>LAN Requests</h3>
-
-        <p>Beginning with Android 17 (API 37), Android’s WebView cannot make requests to devices on the LAN (Local Area Network) unless the
-            <a href="https://developer.android.com/reference/android/Manifest.permission.html#ACCESS_LOCAL_NETWORK">Nearby Devices</a> permission has been granted.
-            This is a dangerous permission that must be explicitly enabled by the user.  Devices on the LAN include everything that is on the same subnet as Privacy Browser, like the web interface of a wireless router.
-            Privacy Browser does not prompt for this permission, and there is no need for users to enable it if they only want to browse the general internet.
-            Users who need to be able to load web pages from the LAN can grant this permission in the OS settings for Privacy Browser.</p>
+        <h3><svg class="header"><use href="../shared_images/local_activity.svg#icon"/></svg> Richieste LAN</h3>
+
+        <p>A partire da Android 17 (API 37), la webview di Android non può inviare richieste a dispositivi sulla LAN (Local Area Network) a meno che l'autorizzazione
+            <a href="https://developer.android.com/reference/android/Manifest.permission.html#ACCESS_LOCAL_NETWORK">Dispositivi nelle vicinanze</a> non sia stata garantita.
+            Si tratta di una autorizzazione pericolosa che deve essere abilitata esplicitamente dall'utente.
+            I dispositivi presenti sulla LAN includono tutto ciò che è presente sulla stessa subnet di Privacy Browser, come l'interfaccia web di un router wireless.
+            Privacy Browser non chiede questa autorizzazione, e non c'è nessuna necessità per gli utenti di abilitarla se tutto ciò che fogliono fare è solo navigare su internet.
+            Gli utenti che vogliono essere in grado di caricare pagine web dalla LAN possono garantire questa autorizzazione nelle impostazioni del sistema operativo per Privacy Browser.</p>
 
         <img class="center224" src="images/nearby_devices.png" alt="Nearby Devices Permission"/>
     </body>
index 7e34b2a2e2f16c077d5f26d6ba8f887222400755..fb394e02b866ce6174a404bed957ffe654c49d3f 100644 (file)
@@ -77,7 +77,7 @@
             A more detailed description of how the filter list entries are processed is available at <a href="https://www.stoutner.com/privacy-browser-android/filter-lists/">stoutner.com</a>.</p>
 
 
-        <h3>LAN Requests</h3>
+        <h3><svg class="header"><use href="../shared_images/local_activity.svg#icon"/></svg> LAN Requests</h3>
 
         <p>Beginning with Android 17 (API 37), Android’s WebView cannot make requests to devices on the LAN (Local Area Network) unless the
             <a href="https://developer.android.com/reference/android/Manifest.permission.html#ACCESS_LOCAL_NETWORK">Nearby Devices</a> permission has been granted.
index e2f9bd6737a034c09464760006e2019bdf95f2d2..0a64e5a3e2fdb20d208f396ffa567c91d9137b7d 100644 (file)
@@ -39,8 +39,8 @@
 
         <h3>Устройства поблизости</h3>
         <p><a href="https://developer.android.com/reference/android/Manifest.permission.html#ACCESS_LOCAL_NETWORK">android.permission.<wbr>ACCESS_LOCAL_NETWORK</a></p>
-        <p>This is a dangerous permission, meaning users must manually enable it if they desire the functionality.  It was added in API 37 (Android 17).
-            It is required to load web pages on the Local Area Network (LAN), like the web interface of a wireless router.</p>
+        <p>Это опасное разрешение, то есть пользователи должны включить его вручную, если им нужна эта функциональность. Оно было добавлено в API 37 (Android 17).
+            Оно требуется для загрузки веб-страниц в локальной сети (LAN), например веб-интерфейса беспроводного роутера.</p>
 
         <h3><a href="https://developer.android.com/about/versions/13/features#runtime-receivers">
             com.stoutner.privacybrowser.<wbr>standard.<wbr>DYNAMIC_<wbr>RECEIVER_<wbr>NOT_<wbr>EXPORTED_<wbr>PERMISSION</a></h3>
index 826778ff6c876a08b84301f09a3a110d5c68011e..4c424b876005203cf7f790c522aa90cd7816e346 100644 (file)
             Более подробное описание обработки записей фильтров доступно на странице <a href="https://www.stoutner.com/privacy-browser-android/filter-lists/">stoutner.com</a>.</p>
 
 
-        <h3>LAN Requests</h3>
+        <h3><svg class="header"><use href="../shared_images/local_activity.svg#icon"/></svg> Запросы в локальной сети</h3>
 
-        <p>Beginning with Android 17 (API 37), Android’s WebView cannot make requests to devices on the LAN (Local Area Network) unless the
-            <a href="https://developer.android.com/reference/android/Manifest.permission.html#ACCESS_LOCAL_NETWORK">Nearby Devices</a> permission has been granted.
-            This is a dangerous permission that must be explicitly enabled by the user.  Devices on the LAN include everything that is on the same subnet as Privacy Browser, like the web interface of a wireless router.
-            Privacy Browser does not prompt for this permission, and there is no need for users to enable it if they only want to browse the general internet.
-            Users who need to be able to load web pages from the LAN can grant this permission in the OS settings for Privacy Browser.</p>
+        <p>Начиная с Android 17 (API 37), WebView в Android не может отправлять запросы к устройствам в локальной сети (LAN), если не предоставлено разрешение
+            <a href="https://developer.android.com/reference/android/Manifest.permission.html#ACCESS_LOCAL_NETWORK">«Устройства поблизости»</a>.
+            Это опасное разрешение, которое пользователь должен включить явным образом.
+            К устройствам в локальной сети относится все, что находится в той же подсети, что и Privacy Browser, например веб-интерфейс беспроводного роутера.
+            Privacy Browser не запрашивает это разрешение, и пользователям не нужно его включать, если они хотят просто просматривать интернет.
+            Пользователи, которым нужна возможность загружать веб-страницы из локальной сети, могут предоставить это разрешение для Privacy Browser в настройках ОС.</p>
 
-        <img class="center224" src="images/nearby_devices.png" alt="Nearby Devices Permission"/>
+        <img class="center224" src="images/nearby_devices.png" alt="Разрешение Устройства поблизости"/>
     </body>
 </html>
index 05e51f0f8be21d05cdbeefcf7a6a56ec32df9b36..9882ff2c7806fbc0d4a045da596771516b5baa46 100644 (file)
@@ -74,7 +74,7 @@
             A more detailed description of how the filter list entries are processed is available at <a href="https://www.stoutner.com/privacy-browser-android/filter-lists/">stoutner.com</a>.</p>
 
 
-        <h3>LAN Requests</h3>
+        <h3><svg class="header"><use href="../shared_images/local_activity.svg#icon"/></svg> LAN Requests</h3>
 
         <p>Beginning with Android 17 (API 37), Android’s WebView cannot make requests to devices on the LAN (Local Area Network) unless the
             <a href="https://developer.android.com/reference/android/Manifest.permission.html#ACCESS_LOCAL_NETWORK">Nearby Devices</a> permission has been granted.
index 4cbded693b2f8654707eb2a73d0b5dda3589e3a0..3d8135608ff83431f0f6b0c0847e1b4ae0d2cd9a 100644 (file)
@@ -74,7 +74,7 @@
             A more detailed description of how the filter list entries are processed is available at <a href="https://www.stoutner.com/privacy-browser-android/filter-lists/">stoutner.com</a>.</p>
 
 
-        <h3>LAN Requests</h3>
+        <h3><svg class="header"><use href="../shared_images/local_activity.svg#icon"/></svg> LAN Requests</h3>
 
         <p>Beginning with Android 17 (API 37), Android’s WebView cannot make requests to devices on the LAN (Local Area Network) unless the
             <a href="https://developer.android.com/reference/android/Manifest.permission.html#ACCESS_LOCAL_NETWORK">Nearby Devices</a> permission has been granted.
index 41953397932a02d162197e3f715ab83d21895e1b..ae0e78979f138ddb0d6b3f130fb497779bdd4e06 100644 (file)
@@ -24,6 +24,7 @@ import android.content.pm.PackageManager
 import android.net.Uri
 import android.os.Bundle
 import android.os.Handler
+import android.provider.OpenableColumns
 import android.text.Editable
 import android.text.TextWatcher
 import android.view.View
@@ -61,7 +62,6 @@ import java.io.File
 import java.io.FileInputStream
 import java.io.FileNotFoundException
 import java.io.FileOutputStream
-import java.lang.Exception
 import java.nio.charset.StandardCharsets
 import java.security.MessageDigest
 import java.security.SecureRandom
@@ -72,6 +72,7 @@ import javax.crypto.CipherInputStream
 import javax.crypto.CipherOutputStream
 import javax.crypto.spec.GCMParameterSpec
 import javax.crypto.spec.SecretKeySpec
+
 import kotlin.system.exitProcess
 
 // Define the encryption constants.
@@ -80,36 +81,36 @@ private const val PASSWORD_ENCRYPTION = 1
 private const val OPENPGP_ENCRYPTION = 2
 
 // Define the saved instance state constants.
-private const val ENCRYPTION_PASSWORD_TEXTINPUTLAYOUT_VISIBILITY = "A"
-private const val OPEN_KEYCHAIN_REQUIRED_TEXTVIEW_VISIBILITY = "B"
-private const val SETTINGS_FILE_LOCATION_CARDVIEW_VISIBILITY = "C"
-private const val SETTINGS_FILE_NAME_LINEARLAYOUT_VISIBILITY = "D"
-private const val OPEN_KEYCHAIN_IMPORT_INSTRUCTIONS_TEXTVIEW_VISIBILITY = "E"
-private const val SETTINGS_IMPORT_EXPORT_BUTTON_VISIBILITY = "F"
-private const val SETTINGS_FILE_NAME_TEXT = "G"
-private const val SETTINGS_IMPORT_EXPORT_BUTTON_TEXT = "H"
-private const val BOOKMARKS_FILE_NAME_LINEARLAYOUT_VISIBILITY = "I"
-private const val BOOKMARKS_IMPORT_EXPORT_BUTTON_VISIBILITY = "J"
-private const val BOOKMARKS_FILE_NAME_TEXT = "K"
-private const val BOOKMARKS_IMPORT_EXPORT_BUTTON_TEXT = "L"
+private const val BOOKMARKS_IMPORT_EXPORT_BUTTON_STRING = "bookmarks_import_export_button_string"
+private const val BOOKMARKS_IMPORT_EXPORT_BUTTON_VISIBILITY = "bookmarks_import_export_button_visibility"
+private const val BOOKMARKS_URI_LINEARLAYOUT_VISIBILITY = "bookmarks_uri_linearlayout_visibility"
+private const val BOOKMARKS_URI_STRING = "booksmarks_uri_string"
+private const val ENCRYPTION_PASSWORD_TEXTINPUTLAYOUT_VISIBILITY = "encryption_password_textinputlayout_visibility"
+private const val OPEN_KEYCHAIN_IMPORT_INSTRUCTIONS_TEXTVIEW_VISIBILITY = "open_keychian_import_instructions_textview_visibility"
+private const val OPEN_KEYCHAIN_REQUIRED_TEXTVIEW_VISIBILITY = "open_keychain_required_textview_visibilty"
+private const val SETTINGS_FILE_LOCATION_CARDVIEW_VISIBILITY = "settings_file_location_cardview_visibility"
+private const val SETTINGS_IMPORT_EXPORT_BUTTON_TEXT = "settings_import_export_button_text"
+private const val SETTINGS_IMPORT_EXPORT_BUTTON_VISIBILITY = "settings_import_export_button_visibility"
+private const val SETTINGS_URI_LINEARLAYOUT_VISIBILITY = "settings_uri_linearlayout_visibility"
+private const val SETTINGS_URI_STRING = "settings_uri_string"
 
 class ImportExportActivity : AppCompatActivity() {
     // Define the class views.
-    private lateinit var scrollView: ScrollView
-    private lateinit var encryptionSpinner: Spinner
-    private lateinit var encryptionPasswordTextInputLayout: TextInputLayout
+    private lateinit var bookmarksUriEditText: EditText
+    private lateinit var bookmarksImportExportButton: Button
+    private lateinit var bookmarksImportRadioButton: RadioButton
+    private lateinit var bookmarksUriLinearLayout: LinearLayout
     private lateinit var encryptionPasswordEditText: EditText
+    private lateinit var encryptionPasswordTextInputLayout: TextInputLayout
+    private lateinit var encryptionSpinner: Spinner
+    private lateinit var openKeychainImportInstructionsTextView: TextView
     private lateinit var openKeychainRequiredTextView: TextView
+    private lateinit var scrollView: ScrollView
     private lateinit var settingsFileLocationCardView: CardView
-    private lateinit var settingsImportRadioButton: RadioButton
-    private lateinit var settingsFileNameLinearLayout: LinearLayout
-    private lateinit var settingsFileNameEditText: EditText
-    private lateinit var openKeychainImportInstructionsTextView: TextView
     private lateinit var settingsImportExportButton: Button
-    private lateinit var bookmarksImportRadioButton: RadioButton
-    private lateinit var bookmarksFileNameLinearLayout: LinearLayout
-    private lateinit var bookmarksFileNameEditText: EditText
-    private lateinit var bookmarksImportExportButton: Button
+    private lateinit var settingsImportRadioButton: RadioButton
+    private lateinit var settingsUriEditText: EditText
+    private lateinit var settingsUriLinearLayout: LinearLayout
 
     // Define the class variables.
     private lateinit var fileProviderDirectory: File
@@ -121,28 +122,28 @@ class ImportExportActivity : AppCompatActivity() {
     private val settingsBrowseForImportActivityResultLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { fileUri: Uri? ->
         // Only do something if the user didn't press back from the file picker.
         if (fileUri != null) {
-            // Get the file name string from the URI.
-            val fileNameString = fileUri.toString()
+            // Convert the URI to a string.
+            val fileUriString = fileUri.toString()
 
-            // Set the settings file name text.
-            settingsFileNameEditText.setText(fileNameString)
+            // Set the settings URI text.
+            settingsUriEditText.setText(fileUriString)
 
-            // Move the cursor to the end of the file name edit text.
-            settingsFileNameEditText.setSelection(fileNameString.length)
+            // Move the cursor to the end of the URI edit text.
+            settingsUriEditText.setSelection(fileUriString.length)
         }
     }
 
     private val settingsBrowseForExportActivityResultLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument("*/*")) { fileUri: Uri? ->
         // Only do something if the user didn't press back from the file picker.
         if (fileUri != null) {
-            // Get the file name string from the URI.
-            val fileNameString = fileUri.toString()
+            // Convert the URI to a string.
+            val fileUriString = fileUri.toString()
 
-            // Set the settings file name text.
-            settingsFileNameEditText.setText(fileNameString)
+            // Set the settings URI text.
+            settingsUriEditText.setText(fileUriString)
 
-            // Move the cursor to the end of the file name edit text.
-            settingsFileNameEditText.setSelection(fileNameString.length)
+            // Move the cursor to the end of the Uri edit text.
+            settingsUriEditText.setSelection(fileUriString.length)
         }
     }
 
@@ -152,11 +153,11 @@ class ImportExportActivity : AppCompatActivity() {
             // Get the file name string from the URI.
             val fileNameString = fileUri.toString()
 
-            // Set the bookmarks file name text.
-            bookmarksFileNameEditText.setText(fileNameString)
+            // Set the bookmarks URI text.
+            bookmarksUriEditText.setText(fileNameString)
 
-            // Move the cursor to the end of the file name edit text.
-            bookmarksFileNameEditText.setSelection(fileNameString.length)
+            // Move the cursor to the end of the URI edit text.
+            bookmarksUriEditText.setSelection(fileNameString.length)
 
             // Scroll to the bottom.
             scrollView.post {
@@ -171,11 +172,11 @@ class ImportExportActivity : AppCompatActivity() {
             // Get the file name string from the URI.
             val fileNameString = fileUri.toString()
 
-            // Set the bookmarks file name text.
-            bookmarksFileNameEditText.setText(fileNameString)
+            // Set the bookmarks URI text.
+            bookmarksUriEditText.setText(fileNameString)
 
             // Move the cursor to the end of the file name edit text.
-            bookmarksFileNameEditText.setSelection(fileNameString.length)
+            bookmarksUriEditText.setSelection(fileNameString.length)
 
             // Scroll to the bottom.
             scrollView.post {
@@ -255,13 +256,15 @@ class ImportExportActivity : AppCompatActivity() {
         settingsFileLocationCardView = findViewById(R.id.settings_file_location_cardview)
         settingsImportRadioButton = findViewById(R.id.settings_import_radiobutton)
         val settingsExportRadioButton = findViewById<RadioButton>(R.id.settings_export_radiobutton)
-        settingsFileNameLinearLayout = findViewById(R.id.settings_file_name_linearlayout)
-        settingsFileNameEditText = findViewById(R.id.settings_file_name_edittext)
+        settingsUriLinearLayout = findViewById(R.id.settings_uri_linearlayout)
+        settingsUriEditText = findViewById(R.id.settings_uri_edittext)
+        val settingsFileNameTextView = findViewById<TextView>(R.id.settings_file_name_textview)
         openKeychainImportInstructionsTextView = findViewById(R.id.openkeychain_import_instructions_textview)
         settingsImportExportButton = findViewById(R.id.settings_import_export_button)
         bookmarksImportRadioButton = findViewById(R.id.bookmarks_import_radiobutton)
-        bookmarksFileNameLinearLayout = findViewById(R.id.bookmarks_file_name_linearlayout)
-        bookmarksFileNameEditText = findViewById(R.id.bookmarks_file_name_edittext)
+        bookmarksUriLinearLayout = findViewById(R.id.bookmarks_uri_linearlayout)
+        bookmarksUriEditText = findViewById(R.id.bookmarks_uri_edittext)
+        val bookmarksFileNameTextView = findViewById<TextView>(R.id.bookmarks_file_name_textview)
         bookmarksImportExportButton = findViewById(R.id.bookmarks_import_export_button)
 
         // Create an array adapter for the spinner.
@@ -286,16 +289,16 @@ class ImportExportActivity : AppCompatActivity() {
                         // Show the file location card.
                         settingsFileLocationCardView.visibility = View.VISIBLE
 
-                        // Show the file name linear layout if either import or export is checked.
+                        // Show the URI linear layout if either import or export is checked.
                         if (settingsImportRadioButton.isChecked || settingsExportRadioButton.isChecked)
-                            settingsFileNameLinearLayout.visibility = View.VISIBLE
+                            settingsUriLinearLayout.visibility = View.VISIBLE
 
                         // Reset the text of the import button, which may have been changed to `Decrypt`.
                         if (settingsImportRadioButton.isChecked)
                             settingsImportExportButton.setText(R.string.import_button)
 
-                        // Clear the file name edit text.
-                        settingsFileNameEditText.text.clear()
+                        // Clear the texts.
+                        settingsUriEditText.text.clear()
 
                         // Disable the import/export button.
                         settingsImportExportButton.isEnabled = false
@@ -312,16 +315,16 @@ class ImportExportActivity : AppCompatActivity() {
                         // Show the file location card.
                         settingsFileLocationCardView.visibility = View.VISIBLE
 
-                        // Show the file name linear layout if either import or export is checked.
+                        // Show the URI linear layout if either import or export is checked.
                         if (settingsImportRadioButton.isChecked || settingsExportRadioButton.isChecked)
-                            settingsFileNameLinearLayout.visibility = View.VISIBLE
+                            settingsUriLinearLayout.visibility = View.VISIBLE
 
                         // Reset the text of the import button, which may have been changed to `Decrypt`.
                         if (settingsImportRadioButton.isChecked)
                             settingsImportExportButton.setText(R.string.import_button)
 
-                        // Clear the file name edit text.
-                        settingsFileNameEditText.text.clear()
+                        // Clear the texts.
+                        settingsUriEditText.text.clear()
 
                         // Disable the import/export button.
                         settingsImportExportButton.isEnabled = false
@@ -338,21 +341,21 @@ class ImportExportActivity : AppCompatActivity() {
 
                             // Update the layout based on the checked radio button.
                             if (settingsImportRadioButton.isChecked) {
-                                // Show the file name linear layout and the OpenKeychain import instructions.
-                                settingsFileNameLinearLayout.visibility = View.VISIBLE
+                                // Show the URI linear layout and the OpenKeychain import instructions.
+                                settingsUriLinearLayout.visibility = View.VISIBLE
                                 openKeychainImportInstructionsTextView.visibility = View.VISIBLE
 
                                 // Set the text of the import button to be `Decrypt`.
                                 settingsImportExportButton.setText(R.string.decrypt)
 
-                                // Clear the file name edit text.
-                                settingsFileNameEditText.text.clear()
+                                // Clear the texts.
+                                settingsUriEditText.text.clear()
 
                                 // Disable the import/export button.
                                 settingsImportExportButton.isEnabled = false
                             } else if (settingsExportRadioButton.isChecked) {
-                                // Hide the file name linear layout and the OpenKeychain import instructions.
-                                settingsFileNameLinearLayout.visibility = View.GONE
+                                // Hide the URI linear layout and the OpenKeychain import instructions.
+                                settingsUriLinearLayout.visibility = View.GONE
                                 openKeychainImportInstructionsTextView.visibility = View.GONE
 
                                 // Enable the export button.
@@ -383,13 +386,13 @@ class ImportExportActivity : AppCompatActivity() {
             }
 
             override fun afterTextChanged(s: Editable) {
-                // Enable the import/export button if both the file string and the password are populated.
-                settingsImportExportButton.isEnabled = settingsFileNameEditText.text.toString().isNotEmpty() && encryptionPasswordEditText.text.toString().isNotEmpty()
+                // Enable the import/export button if both the URI string and the password are populated.
+                settingsImportExportButton.isEnabled = settingsUriEditText.text.toString().isNotEmpty() && encryptionPasswordEditText.text.toString().isNotEmpty()
             }
         })
 
-        // Update the UI when the settings file name edit text changes.
-        settingsFileNameEditText.addTextChangedListener(object : TextWatcher {
+        // Update the UI when the settings URI edit text changes.
+        settingsUriEditText.addTextChangedListener(object : TextWatcher {
             override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
                 // Do nothing.
             }
@@ -398,20 +401,65 @@ class ImportExportActivity : AppCompatActivity() {
                 // Do nothing.
             }
 
-            override fun afterTextChanged(s: Editable) {
+            override fun afterTextChanged(editable: Editable) {
                 // Adjust the UI according to the encryption spinner position.
                 if (encryptionSpinner.selectedItemPosition == PASSWORD_ENCRYPTION) {
-                    // Enable the settings import/export button if both the file name and the password are populated.
-                    settingsImportExportButton.isEnabled = settingsFileNameEditText.text.toString().isNotEmpty() && encryptionPasswordEditText.text.toString().isNotEmpty()
+                    // Enable the settings import/export button if both the URI and the password are populated.
+                    settingsImportExportButton.isEnabled = settingsUriEditText.text.toString().isNotEmpty() && encryptionPasswordEditText.text.toString().isNotEmpty()
                 } else {
-                    // Enable the settings import/export button if the file name is populated.
-                    settingsImportExportButton.isEnabled = settingsFileNameEditText.text.toString().isNotEmpty()
+                    // Enable the settings import/export button if the URI is populated.
+                    settingsImportExportButton.isEnabled = settingsUriEditText.text.toString().isNotEmpty()
+                }
+
+                // Update the file name text view.
+                if (editable.isEmpty()) {  // The URI is empty.
+                    // Hide the settings file name text view.
+                    settingsFileNameTextView.visibility = View.GONE
+                } else {  // The URI is not empty.
+                    // Create a file name URI.
+                    val fileNameUri = editable.toString().toUri()
+
+                    // Create a projection string array (the list of columns to request in the content resolver query).
+                    val projectionStringArray = arrayOf(OpenableColumns.DISPLAY_NAME)
+
+                    // Try to get the file name.  This can fail if the URI has been edited in such a way that it doesn't match a recent API request by the browse button.
+                    try {
+                        // Get the file name cursor.
+                        val fileNameCursor = contentResolver.query(fileNameUri, projectionStringArray, null, null)
+
+                        // Initialize a file name string.
+                        var fileNameString = ""
+
+                        // Get the file name from the cursor.
+                        if ((fileNameCursor != null) && (fileNameCursor.count != 0)) {
+                            // Move to the first entry in the cursor.
+                            fileNameCursor.moveToFirst()
+
+                            // Populate the file name string.
+                            fileNameString = fileNameCursor.getString(0)
+
+                            // Close the cursor.
+                            fileNameCursor.close()
+                        }
+
+                        // Populate the file name text view if the file name string is not empty.
+                        if (fileNameString.isNotEmpty()) {
+                            // Populate the settings file name text.
+                            settingsFileNameTextView.text = fileNameString
+
+                            // Show the settings file name text view.
+                            settingsFileNameTextView.visibility = View.VISIBLE
+                        }
+                    } catch (_: Exception) {  // The URI could not be converted into a file name.
+                        // Hide the settings file name text view.
+                        settingsFileNameTextView.visibility = View.GONE
+                    }
                 }
             }
         })
 
-        // Update the UI when the bookmarks file name edit text changes.
-        bookmarksFileNameEditText.addTextChangedListener(object : TextWatcher {
+        // Update the UI when the bookmarks URI edit text changes.
+        bookmarksUriEditText.addTextChangedListener(object : TextWatcher {
             override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
                 // Do nothing.
             }
@@ -420,9 +468,54 @@ class ImportExportActivity : AppCompatActivity() {
                 // Do nothing.
             }
 
-            override fun afterTextChanged(s: Editable) {
+            override fun afterTextChanged(editable: Editable) {
                 // Enable the bookmarks import/export button if the file name is populated.
-                bookmarksImportExportButton.isEnabled = bookmarksFileNameEditText.text.toString().isNotEmpty()
+                bookmarksImportExportButton.isEnabled = bookmarksUriEditText.text.toString().isNotEmpty()
+
+                // Update the bookmarks file name text view.
+                if (editable.isEmpty()) {  // The URI is empty.
+                    // Hide the bookmarks file name text view.
+                    bookmarksFileNameTextView.visibility = View.GONE
+                } else {  // The URI is not empty.
+                    // Create a file name URI.
+                    val fileNameUri = editable.toString().toUri()
+
+                    // Create a projection string array (the list of columns to request in the content resolver query).
+                    val projectionStringArray = arrayOf(OpenableColumns.DISPLAY_NAME)
+
+                    // Try to get the file name.  This can fail if the URI has been edited in such a way that it doesn't match a recent API request by the browse button.
+                    try {
+                        // Get the file name cursor.
+                        val fileNameCursor = contentResolver.query(fileNameUri, projectionStringArray, null, null)
+
+                        // Initialize a file name string.
+                        var fileNameString = ""
+
+                        // Get the file name from the cursor.
+                        if ((fileNameCursor != null) && (fileNameCursor.count != 0)) {
+                            // Move to the first entry in the cursor.
+                            fileNameCursor.moveToFirst()
+
+                            // Populate the file name string.
+                            fileNameString = fileNameCursor.getString(0)
+
+                            // Close the cursor.
+                            fileNameCursor.close()
+                        }
+
+                        // Populate the file name text view if the file name string is not empty.
+                        if (fileNameString.isNotEmpty()) {
+                            // Populate the bookmarks file name text.
+                            bookmarksFileNameTextView.text = fileNameString
+
+                            // Show the bookmarks file name text view.
+                            bookmarksFileNameTextView.visibility = View.VISIBLE
+                        }
+                    } catch (_: Exception) {  // The URI could not be converted into a file name.
+                        // Hide the bookmarks file name text view.
+                        bookmarksFileNameTextView.visibility = View.GONE
+                    }
+                }
             }
         })
 
@@ -431,27 +524,27 @@ class ImportExportActivity : AppCompatActivity() {
             // Initially hide the unneeded views.
             encryptionPasswordTextInputLayout.visibility = View.GONE
             openKeychainRequiredTextView.visibility = View.GONE
-            settingsFileNameLinearLayout.visibility = View.GONE
+            settingsUriLinearLayout.visibility = View.GONE
             openKeychainImportInstructionsTextView.visibility = View.GONE
             settingsImportExportButton.visibility = View.GONE
-            bookmarksFileNameLinearLayout.visibility = View.GONE
+            bookmarksUriLinearLayout.visibility = View.GONE
             bookmarksImportExportButton.visibility = View.GONE
         } else {  // The app has been restarted.
             // Restore the visibility of the views.
             encryptionPasswordTextInputLayout.visibility = savedInstanceState.getInt(ENCRYPTION_PASSWORD_TEXTINPUTLAYOUT_VISIBILITY)
             openKeychainRequiredTextView.visibility = savedInstanceState.getInt(OPEN_KEYCHAIN_REQUIRED_TEXTVIEW_VISIBILITY)
             settingsFileLocationCardView.visibility = savedInstanceState.getInt(SETTINGS_FILE_LOCATION_CARDVIEW_VISIBILITY)
-            settingsFileNameLinearLayout.visibility = savedInstanceState.getInt(SETTINGS_FILE_NAME_LINEARLAYOUT_VISIBILITY)
+            settingsUriLinearLayout.visibility = savedInstanceState.getInt(SETTINGS_URI_LINEARLAYOUT_VISIBILITY)
             openKeychainImportInstructionsTextView.visibility = savedInstanceState.getInt(OPEN_KEYCHAIN_IMPORT_INSTRUCTIONS_TEXTVIEW_VISIBILITY)
             settingsImportExportButton.visibility = savedInstanceState.getInt(SETTINGS_IMPORT_EXPORT_BUTTON_VISIBILITY)
-            bookmarksFileNameLinearLayout.visibility = savedInstanceState.getInt(BOOKMARKS_FILE_NAME_LINEARLAYOUT_VISIBILITY)
+            bookmarksUriLinearLayout.visibility = savedInstanceState.getInt(BOOKMARKS_URI_LINEARLAYOUT_VISIBILITY)
             bookmarksImportExportButton.visibility = savedInstanceState.getInt(BOOKMARKS_IMPORT_EXPORT_BUTTON_VISIBILITY)
 
             // Restore the text.
-            settingsFileNameEditText.post { settingsFileNameEditText.setText(savedInstanceState.getString(SETTINGS_FILE_NAME_TEXT)) }
+            settingsUriEditText.post { settingsUriEditText.setText(savedInstanceState.getString(SETTINGS_URI_STRING)) }
             settingsImportExportButton.text = savedInstanceState.getString(SETTINGS_IMPORT_EXPORT_BUTTON_TEXT)
-            bookmarksFileNameEditText.post { bookmarksFileNameEditText.setText(savedInstanceState.getString(BOOKMARKS_FILE_NAME_TEXT)) }
-            bookmarksImportExportButton.text = savedInstanceState.getString(BOOKMARKS_IMPORT_EXPORT_BUTTON_TEXT)
+            bookmarksUriEditText.post { bookmarksUriEditText.setText(savedInstanceState.getString(BOOKMARKS_URI_STRING)) }
+            bookmarksImportExportButton.text = savedInstanceState.getString(BOOKMARKS_IMPORT_EXPORT_BUTTON_STRING)
         }
     }
 
@@ -463,17 +556,17 @@ class ImportExportActivity : AppCompatActivity() {
         outState.putInt(ENCRYPTION_PASSWORD_TEXTINPUTLAYOUT_VISIBILITY, encryptionPasswordTextInputLayout.visibility)
         outState.putInt(OPEN_KEYCHAIN_REQUIRED_TEXTVIEW_VISIBILITY, openKeychainRequiredTextView.visibility)
         outState.putInt(SETTINGS_FILE_LOCATION_CARDVIEW_VISIBILITY, settingsFileLocationCardView.visibility)
-        outState.putInt(SETTINGS_FILE_NAME_LINEARLAYOUT_VISIBILITY, settingsFileNameLinearLayout.visibility)
+        outState.putInt(SETTINGS_URI_LINEARLAYOUT_VISIBILITY, settingsUriLinearLayout.visibility)
         outState.putInt(OPEN_KEYCHAIN_IMPORT_INSTRUCTIONS_TEXTVIEW_VISIBILITY, openKeychainImportInstructionsTextView.visibility)
         outState.putInt(SETTINGS_IMPORT_EXPORT_BUTTON_VISIBILITY, settingsImportExportButton.visibility)
-        outState.putInt(BOOKMARKS_FILE_NAME_LINEARLAYOUT_VISIBILITY, bookmarksFileNameLinearLayout.visibility)
+        outState.putInt(BOOKMARKS_URI_LINEARLAYOUT_VISIBILITY, bookmarksUriLinearLayout.visibility)
         outState.putInt(BOOKMARKS_IMPORT_EXPORT_BUTTON_VISIBILITY, bookmarksImportExportButton.visibility)
 
         // Save the text.
-        outState.putString(SETTINGS_FILE_NAME_TEXT, settingsFileNameEditText.text.toString())
+        outState.putString(SETTINGS_URI_STRING, settingsUriEditText.text.toString())
         outState.putString(SETTINGS_IMPORT_EXPORT_BUTTON_TEXT, settingsImportExportButton.text.toString())
-        outState.putString(BOOKMARKS_FILE_NAME_TEXT, bookmarksFileNameEditText.text.toString())
-        outState.putString(BOOKMARKS_IMPORT_EXPORT_BUTTON_VISIBILITY, bookmarksImportExportButton.text.toString())
+        outState.putString(BOOKMARKS_URI_STRING, bookmarksUriEditText.text.toString())
+        outState.putString(BOOKMARKS_IMPORT_EXPORT_BUTTON_STRING, bookmarksImportExportButton.text.toString())
     }
 
     fun onClickBookmarksRadioButton(view: View) {
@@ -487,11 +580,11 @@ class ImportExportActivity : AppCompatActivity() {
         }
 
         // Display the bookmarks views.
-        bookmarksFileNameLinearLayout.visibility = View.VISIBLE
+        bookmarksUriLinearLayout.visibility = View.VISIBLE
         bookmarksImportExportButton.visibility = View.VISIBLE
 
-        // Clear the bookmarks file name edit text.
-        bookmarksFileNameEditText.text.clear()
+        // Clear the bookmarks URI edit text.
+        bookmarksUriEditText.text.clear()
 
         // Disable the bookmarks import/export button.
         bookmarksImportExportButton.isEnabled = false
@@ -521,11 +614,11 @@ class ImportExportActivity : AppCompatActivity() {
             }
 
             // Display the views.
-            settingsFileNameLinearLayout.visibility = View.VISIBLE
+            settingsUriLinearLayout.visibility = View.VISIBLE
             settingsImportExportButton.visibility = View.VISIBLE
 
-            // Clear the settings file name edit text.
-            settingsFileNameEditText.text.clear()
+            // Clear the settings texts.
+            settingsUriEditText.text.clear()
 
             // Disable the settings import/export button.
             settingsImportExportButton.isEnabled = false
@@ -541,17 +634,17 @@ class ImportExportActivity : AppCompatActivity() {
 
             // Check to see if OpenPGP encryption is selected.
             if (encryptionSpinner.selectedItemPosition == OPENPGP_ENCRYPTION) {  // OpenPGP encryption is selected.
-                // Hide the settings file name views.
-                settingsFileNameLinearLayout.visibility = View.GONE
+                // Hide the settings URI views.
+                settingsUriLinearLayout.visibility = View.GONE
 
                 // Enable the settings export button.
                 settingsImportExportButton.isEnabled = true
             } else {  // OpenPGP encryption is not selected.
-                // Show the settings file name view.
-                settingsFileNameLinearLayout.visibility = View.VISIBLE
+                // Show the settings URI views.
+                settingsUriLinearLayout.visibility = View.VISIBLE
 
-                // Clear the settings file name edit text.
-                settingsFileNameEditText.text.clear()
+                // Clear the settings texts.
+                settingsUriEditText.text.clear()
 
                 // Disable the settings import/export button.
                 settingsImportExportButton.isEnabled = false
@@ -588,19 +681,19 @@ class ImportExportActivity : AppCompatActivity() {
         // Instantiate the import/export bookmarks helper.
         val importExportBookmarksHelper = ImportExportBookmarksHelper()
 
-        // Get the file name string.
-        val fileNameString = bookmarksFileNameEditText.text.toString()
+        // Get the file URI string.
+        val fileUriString = bookmarksUriEditText.text.toString()
 
         // Check to see if import or export is selected.
         if (bookmarksImportRadioButton.isChecked) {  // Import is selected.
             // Import the bookmarks.
-            importExportBookmarksHelper.importBookmarks(fileNameString, context = this, scrollView)
+            importExportBookmarksHelper.importBookmarks(fileUriString, context = this, scrollView)
 
             // Repopulate the bookmarks in the main WebView activity.
             MainWebViewActivity.restartFromBookmarksActivity = true
         } else {  // Export is selected.
             // Export the bookmarks.
-            importExportBookmarksHelper.exportBookmarks(fileNameString, context = this, scrollView)
+            importExportBookmarksHelper.exportBookmarks(fileUriString, context = this, scrollView)
         }
     }
 
@@ -613,15 +706,15 @@ class ImportExportActivity : AppCompatActivity() {
             // Initialize the import status string
             var importStatus = ""
 
-            // Get the file name string.
-            val fileNameString = settingsFileNameEditText.text.toString()
+            // Get the file URI string.
+            val fileUriString = settingsUriEditText.text.toString()
 
             // Import according to the encryption type.
             when (encryptionSpinner.selectedItemPosition) {
                 NO_ENCRYPTION -> {
                     try {
                         // Get an input stream for the file name.
-                        val inputStream = contentResolver.openInputStream(fileNameString.toUri())!!
+                        val inputStream = contentResolver.openInputStream(fileUriString.toUri())!!
 
                         // Import the unencrypted file.
                         importStatus = importExportDatabaseHelper.importUnencrypted(inputStream, this)
@@ -643,8 +736,8 @@ class ImportExportActivity : AppCompatActivity() {
                         // Get the encryption password.
                         val encryptionPasswordString = encryptionPasswordEditText.text.toString()
 
-                        // Get an input stream for the file name.
-                        val inputStream = contentResolver.openInputStream(fileNameString.toUri())!!
+                        // Get an input stream for the file URI.
+                        val inputStream = contentResolver.openInputStream(fileUriString.toUri())!!
 
                         // Initialize a salt byte array.  Salt protects against rainbow table attacks.
                         val saltByteArray = ByteArray(32)
@@ -762,8 +855,8 @@ class ImportExportActivity : AppCompatActivity() {
                         // Create a temporary PGP encrypted import file output stream.
                         val temporaryPgpEncryptedImportFileOutputStream = FileOutputStream(temporaryPgpEncryptedImportFile)
 
-                        // Get an input stream for the file name.
-                        val inputStream = contentResolver.openInputStream(fileNameString.toUri())!!
+                        // Get an input stream for the file URI.
+                        val inputStream = contentResolver.openInputStream(fileUriString.toUri())!!
 
                         // Create a transfer byte array.
                         val transferByteArray = ByteArray(1024)
@@ -808,13 +901,13 @@ class ImportExportActivity : AppCompatActivity() {
 
             // Display a snack bar with the import error if it was unsuccessful.
             if (importStatus != IMPORT_SUCCESSFUL)
-                Snackbar.make(settingsFileNameEditText, getString(R.string.import_failed, importStatus), Snackbar.LENGTH_INDEFINITE).show()
+                Snackbar.make(settingsUriEditText, getString(R.string.import_failed, importStatus), Snackbar.LENGTH_INDEFINITE).show()
         } else {  // Export is selected.
             // Export according to the encryption type.
             when (encryptionSpinner.selectedItemPosition) {
                 NO_ENCRYPTION -> {
-                    // Get the file name string.
-                    val noEncryptionFileNameString = settingsFileNameEditText.text.toString()
+                    // Get the file URI string.
+                    val noEncryptionFileNameString = settingsUriEditText.text.toString()
 
                     try {
                         // Get the export file output stream, truncating any existing content.
@@ -828,12 +921,12 @@ class ImportExportActivity : AppCompatActivity() {
 
                         // Display an export disposition snackbar.
                         if (noEncryptionExportStatus == EXPORT_SUCCESSFUL)
-                            Snackbar.make(settingsFileNameEditText, getString(R.string.export_successful), Snackbar.LENGTH_SHORT).show()
+                            Snackbar.make(settingsUriEditText, getString(R.string.export_successful), Snackbar.LENGTH_SHORT).show()
                         else
-                            Snackbar.make(settingsFileNameEditText, getString(R.string.export_failed, noEncryptionExportStatus), Snackbar.LENGTH_INDEFINITE).show()
+                            Snackbar.make(settingsUriEditText, getString(R.string.export_failed, noEncryptionExportStatus), Snackbar.LENGTH_INDEFINITE).show()
                     } catch (fileNotFoundException: FileNotFoundException) {
                         // Display a snackbar with the exception.
-                        Snackbar.make(settingsFileNameEditText, getString(R.string.export_failed, fileNotFoundException), Snackbar.LENGTH_INDEFINITE).show()
+                        Snackbar.make(settingsUriEditText, getString(R.string.export_failed, fileNotFoundException), Snackbar.LENGTH_INDEFINITE).show()
                     }
                 }
 
@@ -906,7 +999,7 @@ class ImportExportActivity : AppCompatActivity() {
                         cipher.init(Cipher.ENCRYPT_MODE, secretKey, gcmParameterSpec)
 
                         // Get the file name string.
-                        val passwordEncryptionFileNameString = settingsFileNameEditText.text.toString()
+                        val passwordEncryptionFileNameString = settingsUriEditText.text.toString()
 
                         // Get the export file output stream, truncating any existing content.
                         val exportFileOutputStream = contentResolver.openOutputStream(passwordEncryptionFileNameString.toUri(), "wt")!!
@@ -947,12 +1040,12 @@ class ImportExportActivity : AppCompatActivity() {
 
                         // Display an export disposition snackbar.
                         if (passwordEncryptionExportStatus == EXPORT_SUCCESSFUL)
-                            Snackbar.make(settingsFileNameEditText, getString(R.string.export_successful), Snackbar.LENGTH_SHORT).show()
+                            Snackbar.make(settingsUriEditText, getString(R.string.export_successful), Snackbar.LENGTH_SHORT).show()
                         else
-                            Snackbar.make(settingsFileNameEditText, getString(R.string.export_failed, passwordEncryptionExportStatus), Snackbar.LENGTH_INDEFINITE).show()
+                            Snackbar.make(settingsUriEditText, getString(R.string.export_failed, passwordEncryptionExportStatus), Snackbar.LENGTH_INDEFINITE).show()
                     } catch (exception: Exception) {
                         // Display a snackbar with the exception.
-                        Snackbar.make(settingsFileNameEditText, getString(R.string.export_failed, exception), Snackbar.LENGTH_INDEFINITE).show()
+                        Snackbar.make(settingsUriEditText, getString(R.string.export_failed, exception), Snackbar.LENGTH_INDEFINITE).show()
                     }
                 }
 
@@ -986,7 +1079,7 @@ class ImportExportActivity : AppCompatActivity() {
 
                         // Display an export error snackbar if the temporary pre-encrypted export failed.
                         if (openpgpEncryptionExportStatus != EXPORT_SUCCESSFUL)
-                            Snackbar.make(settingsFileNameEditText, getString(R.string.export_failed, openpgpEncryptionExportStatus), Snackbar.LENGTH_INDEFINITE).show()
+                            Snackbar.make(settingsUriEditText, getString(R.string.export_failed, openpgpEncryptionExportStatus), Snackbar.LENGTH_INDEFINITE).show()
 
                         // Create an encryption intent for OpenKeychain.
                         val openKeychainEncryptIntent = Intent("org.sufficientlysecure.keychain.action.ENCRYPT_DATA")
@@ -1004,7 +1097,7 @@ class ImportExportActivity : AppCompatActivity() {
                         openKeychainEncryptActivityResultLauncher.launch(openKeychainEncryptIntent)
                     } catch (exception: Exception) {
                         // Display a snackbar with the exception.
-                        Snackbar.make(settingsFileNameEditText, getString(R.string.export_failed, exception), Snackbar.LENGTH_INDEFINITE).show()
+                        Snackbar.make(settingsUriEditText, getString(R.string.export_failed, exception), Snackbar.LENGTH_INDEFINITE).show()
                     }
                 }
             }
index bbeaef0a83f6fb73c6f50ea2db0050a684b49360..749946b80264dc5217936a0329fbdb09223a7a48 100644 (file)
@@ -5913,7 +5913,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
         val dialog = dialogFragment.dialog!!
 
         // Get handles for the views.
-        val fileNameEditText = dialog.findViewById<EditText>(R.id.file_name_edittext)
+        val fileNameEditText = dialog.findViewById<EditText>(R.id.file_uri_edittext)
         val mhtCheckBox = dialog.findViewById<CheckBox>(R.id.mht_checkbox)
 
         // Get the file path string.
index 7f6bcb3a7cf6c0e8cfca1165eebb5493ea6db6a3..dc16f8d9c36d7b463614f61e60962b96cbd1927a 100644 (file)
@@ -1,5 +1,5 @@
 /* SPDX-License-Identifier: GPL-3.0-or-later
- * SPDX-FileCopyrightText: 2016-2025 Soren Stoutner <soren@stoutner.com>
+ * SPDX-FileCopyrightText: 2016-2026 Soren Stoutner <soren@stoutner.com>
  *
  * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android/>.
  *
index 63e28ef921994829f87d81003c97f68e60548080..d4cf6b265f31634f67e29df68c0cab547e39a93b 100644 (file)
@@ -1,5 +1,5 @@
 /* SPDX-License-Identifier: GPL-3.0-or-later
- * SPDX-FileCopyrightText: 2019-2023, 2025 Soren Stoutner <soren@stoutner.com>
+ * SPDX-FileCopyrightText: 2019-2023, 2025-2026 Soren Stoutner <soren@stoutner.com>
  *
  * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android/>.
  *
@@ -25,6 +25,7 @@ import android.content.DialogInterface
 import android.net.Uri
 import android.os.Build
 import android.os.Bundle
+import android.provider.OpenableColumns
 import android.text.Editable
 import android.text.TextWatcher
 import android.view.View
@@ -36,6 +37,7 @@ import android.widget.TextView
 
 import androidx.activity.result.contract.ActivityResultContracts
 import androidx.appcompat.app.AlertDialog
+import androidx.core.net.toUri
 import androidx.core.view.isVisible
 import androidx.fragment.app.DialogFragment
 import androidx.preference.PreferenceManager
@@ -50,7 +52,7 @@ class OpenDialog : DialogFragment() {
     private lateinit var openListener: OpenListener
 
     // Declare the class views.
-    private lateinit var fileNameEditText: EditText
+    private lateinit var fileUriEditText: EditText
     private lateinit var mhtExplanationTextView: TextView
 
     // The public interface is used to send information back to the parent activity.
@@ -70,14 +72,14 @@ class OpenDialog : DialogFragment() {
     private val browseActivityResultLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { fileUri: Uri? ->
         // Only do something if the user didn't press back from the file picker.
         if (fileUri != null) {
-            // Get the file name string from the URI.
-            val fileNameString = fileUri.toString()
+            // Convert the file URI to a string.
+            val fileUriString = fileUri.toString()
 
-            // Set the file name text.
-            fileNameEditText.setText(fileNameString)
+            // Populate the file URI edit text.
+            fileUriEditText.setText(fileUriString)
 
-            // Move the cursor to the end of the file name edit text.
-            fileNameEditText.setSelection(fileNameString.length)
+            // Move the cursor to the end of the file URI edit text.
+            fileUriEditText.setSelection(fileUriString.length)
         }
     }
 
@@ -121,8 +123,9 @@ class OpenDialog : DialogFragment() {
         alertDialog.show()
 
         // Get handles for the layout items.
-        fileNameEditText = alertDialog.findViewById(R.id.file_name_edittext)!!
+        fileUriEditText = alertDialog.findViewById(R.id.file_uri_edittext)!!
         val browseButton = alertDialog.findViewById<Button>(R.id.browse_button)!!
+        val fileNameTextView = alertDialog.findViewById<TextView>(R.id.file_name_textview)!!
         val mhtCheckBox = alertDialog.findViewById<CheckBox>(R.id.mht_checkbox)!!
         mhtExplanationTextView = alertDialog.findViewById(R.id.mht_explanation_textview)!!
         val openButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
@@ -136,8 +139,8 @@ class OpenDialog : DialogFragment() {
         else
             mhtCheckBox.visibility = View.GONE
 
-        // Update the status of the open button when the file name changes.
-        fileNameEditText.addTextChangedListener(object : TextWatcher {
+        // Update the status of the open button and the file name text view when the file URI changes.
+        fileUriEditText.addTextChangedListener(object : TextWatcher {
             override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
                 // Do nothing.
             }
@@ -147,11 +150,53 @@ class OpenDialog : DialogFragment() {
             }
 
             override fun afterTextChanged(editable: Editable) {
-                // Get the current file name.
-                val fileNameString = fileNameEditText.text.toString()
-
                 // Enable the open button if the file name is populated.
-                openButton.isEnabled = fileNameString.isNotEmpty()
+                openButton.isEnabled = editable.isNotEmpty()
+
+                // Update the UI.
+                if (editable.isEmpty()) {  // The URI is empty.
+                    // Hide the file name text view.
+                    fileNameTextView.visibility = View.GONE
+                } else {  // The URI is not empty.
+                    // Create a file name URI.
+                    val fileNameUri = editable.toString().toUri()
+
+                    // Create a projection string array (the list of columns to request in the content resolver query).
+                    val projectionStringArray = arrayOf(OpenableColumns.DISPLAY_NAME)
+
+                    // Try to get the file name.  This can fail if the URI has been edited in such a way that it doesn't match a recent API request by the browse button.
+                    try {
+                        // Get the file name cursor.
+                        val fileNameCursor = requireContext().contentResolver.query(fileNameUri, projectionStringArray, null, null)
+
+                        // Initialize a file name string.
+                        var fileNameString = ""
+
+                        // Get the file name from the cursor.
+                        if ((fileNameCursor != null) && (fileNameCursor.count != 0)) {
+                            // Move to the first entry in the cursor.
+                            fileNameCursor.moveToFirst()
+
+                            // Populate the file name string.
+                            fileNameString = fileNameCursor.getString(0)
+
+                            // Close the cursor.
+                            fileNameCursor.close()
+                        }
+
+                        // Populate the file name text view if the file name string is not empty.
+                        if (fileNameString.isNotEmpty()) {
+                            // Populate the file name text.
+                            fileNameTextView.text = fileNameString
+
+                            // Show the file name text view.
+                            fileNameTextView.visibility = View.VISIBLE
+                        }
+                    } catch (_: Exception) {  // The URI could not be converted into a file name.
+                        // Hide the settings file name text view.
+                        fileNameTextView.visibility = View.GONE
+                    }
+                }
             }
         })
 
index 125b155b9a4c9012d53a6534db196e868368567a..32a94dcc65cf02dbb9fb58e9798f831393c32ade 100644 (file)
@@ -2,7 +2,7 @@
 
 <!--
   SPDX-License-Identifier: GPL-3.0-or-later
-  SPDX-FileCopyrightText: 2018-2024 Soren Stoutner <soren@stoutner.com>
+  SPDX-FileCopyrightText: 2018-2024, 2026 Soren Stoutner <soren@stoutner.com>
 
   This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android/>.
 
                                 android:onClick="onClickSettingsRadioButton" />
                         </RadioGroup>
 
-                        <!-- Align the edit text and the select file button horizontally. -->
+                        <!-- Align the URI edit text and the select file button horizontally. -->
                         <LinearLayout
-                            android:id="@+id/settings_file_name_linearlayout"
+                            android:id="@+id/settings_uri_linearlayout"
                             android:layout_height="wrap_content"
                             android:layout_width="match_parent"
                             android:orientation="horizontal"
-                            android:layout_marginTop="10dp">
+                            android:layout_marginTop="10dp" >
 
                             <!-- The text input layout makes the hint float above the edit text. -->
                             <com.google.android.material.textfield.TextInputLayout
 
                                 <!-- `android:inputType="textUri" disables spell check and places an `/` on the main keyboard. -->
                                 <com.google.android.material.textfield.TextInputEditText
-                                    android:id="@+id/settings_file_name_edittext"
+                                    android:id="@+id/settings_uri_edittext"
                                     android:layout_height="wrap_content"
                                     android:layout_width="match_parent"
                                     android:hint="@string/file_name"
                                 android:onClick="settingsBrowse" />
                         </LinearLayout>
 
+                        <!-- Initially hide the file name text view. -->
+                        <TextView
+                            android:id="@+id/settings_file_name_textview"
+                            android:layout_height="wrap_content"
+                            android:layout_width="match_parent"
+                            android:gravity="center_horizontal"
+                            android:visibility="gone" />
+
                         <!-- OpenKeychain import instructions -->
                         <TextView
                             android:id="@+id/openkeychain_import_instructions_textview"
                                 android:onClick="onClickBookmarksRadioButton" />
                         </RadioGroup>
 
-                        <!-- Align the edit text and the select file button horizontally. -->
+                        <!-- Align the URI edit text and the select file button horizontally. -->
                         <LinearLayout
-                            android:id="@+id/bookmarks_file_name_linearlayout"
+                            android:id="@+id/bookmarks_uri_linearlayout"
                             android:layout_height="wrap_content"
                             android:layout_width="match_parent"
                             android:orientation="horizontal"
-                            android:layout_marginTop="10dp">
+                            android:layout_marginTop="10dp" >
 
                             <!-- The text input layout makes the hint float above the edit text. -->
                             <com.google.android.material.textfield.TextInputLayout
 
                                 <!-- `android:inputType="textUri" disables spell check and places an `/` on the main keyboard. -->
                                 <com.google.android.material.textfield.TextInputEditText
-                                    android:id="@+id/bookmarks_file_name_edittext"
+                                    android:id="@+id/bookmarks_uri_edittext"
                                     android:layout_height="wrap_content"
                                     android:layout_width="match_parent"
                                     android:hint="@string/file_name"
                                 android:onClick="bookmarksBrowse" />
                         </LinearLayout>
 
+                        <!-- Initially hide the file name text view. -->
+                        <TextView
+                            android:id="@+id/bookmarks_file_name_textview"
+                            android:layout_height="wrap_content"
+                            android:layout_width="match_parent"
+                            android:gravity="center_horizontal"
+                            android:visibility="gone" />
+
                         <Button
                             android:id="@+id/bookmarks_import_export_button"
                             android:layout_height="wrap_content"
index 99f586ba38149d0c7b19fa2e4f31c4ef1f8e3245..ce23544ad9001695aaa839e5f2b1cd773a9605fe 100644 (file)
@@ -2,7 +2,7 @@
 
 <!--
   SPDX-License-Identifier: GPL-3.0-or-later
-  SPDX-FileCopyrightText: 2018-2024 Soren Stoutner <soren@stoutner.com>
+  SPDX-FileCopyrightText: 2018-2024, 2026 Soren Stoutner <soren@stoutner.com>
 
   This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android/>.
 
                                 android:onClick="onClickSettingsRadioButton" />
                         </RadioGroup>
 
-                        <!-- Align the edit text and the select file button horizontally. -->
+                        <!-- Align the URI edit text and the select file button horizontally. -->
                         <LinearLayout
-                            android:id="@+id/settings_file_name_linearlayout"
+                            android:id="@+id/settings_uri_linearlayout"
                             android:layout_height="wrap_content"
                             android:layout_width="match_parent"
                             android:orientation="horizontal"
-                            android:layout_marginTop="10dp">
+                            android:layout_marginTop="10dp" >
 
                             <!-- The text input layout makes the hint float above the edit text. -->
                             <com.google.android.material.textfield.TextInputLayout
 
                                 <!-- `android:inputType="textUri" disables spell check and places an `/` on the main keyboard. -->
                                 <com.google.android.material.textfield.TextInputEditText
-                                    android:id="@+id/settings_file_name_edittext"
+                                    android:id="@+id/settings_uri_edittext"
                                     android:layout_height="wrap_content"
                                     android:layout_width="match_parent"
                                     android:hint="@string/file_name"
                                 android:onClick="settingsBrowse" />
                         </LinearLayout>
 
+                        <!-- Initially hide the file name text view. -->
+                        <TextView
+                            android:id="@+id/settings_file_name_textview"
+                            android:layout_height="wrap_content"
+                            android:layout_width="match_parent"
+                            android:gravity="center_horizontal"
+                            android:visibility="gone" />
+
                         <!-- OpenKeychain import instructions -->
                         <TextView
                             android:id="@+id/openkeychain_import_instructions_textview"
                                 android:onClick="onClickBookmarksRadioButton" />
                         </RadioGroup>
 
-                        <!-- Align the edit text and the select file button horizontally. -->
+                        <!-- Align the URI edit text and the select file button horizontally. -->
                         <LinearLayout
-                            android:id="@+id/bookmarks_file_name_linearlayout"
+                            android:id="@+id/bookmarks_uri_linearlayout"
                             android:layout_height="wrap_content"
                             android:layout_width="match_parent"
                             android:orientation="horizontal"
-                            android:layout_marginTop="10dp">
+                            android:layout_marginTop="10dp" >
 
                             <!-- The text input layout makes the hint float above the edit text. -->
                             <com.google.android.material.textfield.TextInputLayout
 
                                 <!-- `android:inputType="textUri" disables spell check and places an `/` on the main keyboard. -->
                                 <com.google.android.material.textfield.TextInputEditText
-                                    android:id="@+id/bookmarks_file_name_edittext"
+                                    android:id="@+id/bookmarks_uri_edittext"
                                     android:layout_height="wrap_content"
                                     android:layout_width="match_parent"
                                     android:hint="@string/file_name"
                                 android:onClick="bookmarksBrowse" />
                         </LinearLayout>
 
+                        <!-- Initially hide the file name text view. -->
+                        <TextView
+                            android:id="@+id/bookmarks_file_name_textview"
+                            android:layout_height="wrap_content"
+                            android:layout_width="match_parent"
+                            android:gravity="center_horizontal"
+                            android:visibility="gone" />
+
                         <Button
                             android:id="@+id/bookmarks_import_export_button"
                             android:layout_height="wrap_content"
index f96c715d41f02c95d9c389b4532b2d32c0dd63bc..a8ce2467f1f4eac86a22c0901474533241b97185 100644 (file)
@@ -1,22 +1,23 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <!--
-  Copyright 2016-2017,2022-2023 Soren Stoutner <soren@stoutner.com>.
+  SPDX-License-Identifier: GPL-3.0-or-later
+  SPDX-FileCopyrightText: 2016-2017, 2022-2023 Soren Stoutner <soren@stoutner.com>
 
-  This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
+  This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android/>.
 
-  Privacy Browser Android is free software: you can redistribute it and/or modify
-  it under the terms of the GNU General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
+  This program is free software: you can redistribute it and/or modify it under
+  the terms of the GNU General Public License as published by the Free Software
+  Foundation, either version 3 of the License, or (at your option) any later
+  version.
 
-  Privacy Browser Android is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU General Public License for more details.
+  This program is distributed in the hope that it will be useful, but WITHOUT
+  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+  details.
 
-  You should have received a copy of the GNU General Public License
-  along with Privacy Browser Android.  If not, see <http://www.gnu.org/licenses/>. -->
+  You should have received a copy of the GNU General Public License along with
+  this program.  If not, see <https://www.gnu.org/licenses/>. -->
 
 <!-- `android:dividerHeight` must be specified with `android:divider` or the height will be `0dp`.  In our case the correct divider height is `0dp`. -->
 <ListView
index 76f041c693bee09dcd8e8599df34b3870d0f864f..8f6e66cbdb22a33fb6d5c210f8af6fd3b665489f 100644 (file)
@@ -1,22 +1,23 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <!--
-  Copyright 2016-2017, 2022-2024 Soren Stoutner <soren@stoutner.com>.
+  SPDX-License-Identifier: GPL-3.0-or-later
+  SPDX-FileCopyrightText: 2016-2017, 2022-2024 Soren Stoutner <soren@stoutner.com>
 
   This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android/>.
 
-  Privacy Browser Android is free software: you can redistribute it and/or modify
-  it under the terms of the GNU General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
+  This program is free software: you can redistribute it and/or modify it under
+  the terms of the GNU General Public License as published by the Free Software
+  Foundation, either version 3 of the License, or (at your option) any later
+  version.
 
-  Privacy Browser Android is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU General Public License for more details.
+  This program is distributed in the hope that it will be useful, but WITHOUT
+  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+  details.
 
-  You should have received a copy of the GNU General Public License
-  along with Privacy Browser Android.  If not, see <http://www.gnu.org/licenses/>. -->
+  You should have received a copy of the GNU General Public License along with
+  this program.  If not, see <https://www.gnu.org/licenses/>. -->
 
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
index 6ce370743ab85b5bda8b4e113c6e80840a11fe09..9f4a8fcc03f7a15f0646555e04a1ba6276b61fc4 100644 (file)
@@ -1,22 +1,23 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <!--
-  Copyright © 2019-2022 Soren Stoutner <soren@stoutner.com>.
+  SPDX-License-Identifier: GPL-3.0-or-later
+  SPDX-FileCopyrightText: 2019-2022, 2026 Soren Stoutner <soren@stoutner.com>
 
-  This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
+  This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android/>.
 
-  Privacy Browser Android is free software: you can redistribute it and/or modify
-  it under the terms of the GNU General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
+  This program is free software: you can redistribute it and/or modify it under
+  the terms of the GNU General Public License as published by the Free Software
+  Foundation, either version 3 of the License, or (at your option) any later
+  version.
 
-  Privacy Browser Android is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU General Public License for more details.
+  This program is distributed in the hope that it will be useful, but WITHOUT
+  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+  details.
 
-  You should have received a copy of the GNU General Public License
-  along with Privacy Browser Android.  If not, see <http://www.gnu.org/licenses/>. -->
+  You should have received a copy of the GNU General Public License along with
+  this program.  If not, see <https://www.gnu.org/licenses/>. -->
 
 <ScrollView
     xmlns:android="http://schemas.android.com/apk/res/android"
@@ -28,7 +29,7 @@
         android:layout_width="match_parent"
         android:orientation="vertical" >
 
-        <!-- Align the edit text and the select file button horizontally. -->
+        <!-- Align the URI edit text and the select file button horizontally. -->
         <LinearLayout
             android:layout_height="wrap_content"
             android:layout_width="match_parent"
@@ -45,7 +46,7 @@
 
                 <!-- `android:inputType="textUri"` disables spell check and places an `/` on the main keyboard. -->
                 <com.google.android.material.textfield.TextInputEditText
-                    android:id="@+id/file_name_edittext"
+                    android:id="@+id/file_uri_edittext"
                     android:layout_height="wrap_content"
                     android:layout_width="match_parent"
                     android:hint="@string/file_name"
                 android:text="@string/browse" />
         </LinearLayout>
 
+        <!-- Initially hide the file name text view. -->
+        <TextView
+            android:id="@+id/file_name_textview"
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent"
+            android:gravity="center_horizontal"
+            android:visibility="gone" />
+
         <CheckBox
             android:id="@+id/mht_checkbox"
             android:layout_height="wrap_content"
index 7e572987aea436302a80c0ef2e877a2c9355121d..d23b9202ab6cb9f42a0add96744488843cb55462 100644 (file)
@@ -1,22 +1,23 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <!--
-  Copyright © 2017-2020,2022 Soren Stoutner <soren@stoutner.com>.
+  SPDX-License-Identifier: GPL-3.0-or-later
+  SPDX-FileCopyrightText: 2017-2020, 2022 Soren Stoutner <soren@stoutner.com>
 
-  This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
+  This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android/>.
 
-  Privacy Browser Android is free software: you can redistribute it and/or modify
-  it under the terms of the GNU General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
+  This program is free software: you can redistribute it and/or modify it under
+  the terms of the GNU General Public License as published by the Free Software
+  Foundation, either version 3 of the License, or (at your option) any later
+  version.
 
-  Privacy Browser Android is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU General Public License for more details.
+  This program is distributed in the hope that it will be useful, but WITHOUT
+  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+  details.
 
-  You should have received a copy of the GNU General Public License
-  along with Privacy Browser Android.  If not, see <http://www.gnu.org/licenses/>. -->
+  You should have received a copy of the GNU General Public License along with
+  this program.  If not, see <https://www.gnu.org/licenses/>. -->
 
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
index 2802a2d339973aeba0f8087edb950384faed5f35..68c8b7477ad51ff1c2e5a0d3fd85436506574e19 100644 (file)
@@ -1,22 +1,23 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <!--
-  Copyright © 2017-2022 Soren Stoutner <soren@stoutner.com>.
+  SPDX-License-Identifier: GPL-3.0-or-later
+  SPDX-FileCopyrightText: 2017-2022 Soren Stoutner <soren@stoutner.com>
 
-  This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
+  This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android/>.
 
-  Privacy Browser Android is free software: you can redistribute it and/or modify
-  it under the terms of the GNU General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
+  This program is free software: you can redistribute it and/or modify it under
+  the terms of the GNU General Public License as published by the Free Software
+  Foundation, either version 3 of the License, or (at your option) any later
+  version.
 
-  Privacy Browser Android is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU General Public License for more details.
+  This program is distributed in the hope that it will be useful, but WITHOUT
+  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+  details.
 
-  You should have received a copy of the GNU General Public License
-  along with Privacy Browser Android.  If not, see <http://www.gnu.org/licenses/>. -->
+  You should have received a copy of the GNU General Public License along with
+  this program.  If not, see <https://www.gnu.org/licenses/>. -->
 
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
index d9b1ea6b6ac71bfa70ca5874600bb7a1a4c9d62a..e730e1d83cea237b9af15622704a3ebcb4ec8ad8 100644 (file)
@@ -1,22 +1,23 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <!--
-  Copyright 2019-2022, 2024 Soren Stoutner <soren@stoutner.com>.
+  SPDX-License-Identifier: GPL-3.0-or-later
+  SPDX-FileCopyrightText: 2019-2022, 2024 Soren Stoutner <soren@stoutner.com>
 
-  This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
+  This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android/>.
 
-  Privacy Browser Android is free software: you can redistribute it and/or modify
-  it under the terms of the GNU General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
+  This program is free software: you can redistribute it and/or modify it under
+  the terms of the GNU General Public License as published by the Free Software
+  Foundation, either version 3 of the License, or (at your option) any later
+  version.
 
-  Privacy Browser Android is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU General Public License for more details.
+  This program is distributed in the hope that it will be useful, but WITHOUT
+  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+  details.
 
-  You should have received a copy of the GNU General Public License
-  along with Privacy Browser Android.  If not, see <http://www.gnu.org/licenses/>. -->
+  You should have received a copy of the GNU General Public License along with
+  this program.  If not, see <https://www.gnu.org/licenses/>. -->
 
 <ScrollView
     xmlns:android="http://schemas.android.com/apk/res/android"
index e43ccd12d10c401ae151881cab20d0030418c57f..31b4f5c381486b65ee73ad2b639ae18389660f6d 100644 (file)
@@ -26,7 +26,7 @@ buildscript {
     }
 
     dependencies {
-        classpath 'com.android.tools.build:gradle:9.2.1'
+        classpath 'com.android.tools.build:gradle:9.3.1'
         classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.10'
 
         // NOTE: Do not place your application dependencies here; they belong