]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/commitdiff
Bump the minimum API from 26 to 29. master
authorSoren Stoutner <soren@stoutner.com>
Thu, 30 Apr 2026 17:50:50 +0000 (10:50 -0700)
committerSoren Stoutner <soren@stoutner.com>
Thu, 30 Apr 2026 17:50:50 +0000 (10:50 -0700)
19 files changed:
app/build.gradle
app/src/main/assets/de/about_changelog.html
app/src/main/assets/en/about_changelog.html
app/src/main/assets/es/about_changelog.html
app/src/main/assets/fr/about_changelog.html
app/src/main/assets/it/about_changelog.html
app/src/main/assets/pt-rBR/about_changelog.html
app/src/main/assets/ru/about_changelog.html
app/src/main/assets/tr/about_changelog.html
app/src/main/assets/zh-rCN/about_changelog.html
app/src/main/java/com/stoutner/privacybrowser/activities/ImportExportActivity.kt
app/src/main/java/com/stoutner/privacybrowser/activities/LogcatActivity.kt
app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.kt
app/src/main/java/com/stoutner/privacybrowser/backgroundtasks/GetHeadersBackgroundTask.kt
app/src/main/java/com/stoutner/privacybrowser/fragments/AboutVersionFragment.kt
app/src/main/java/com/stoutner/privacybrowser/fragments/SettingsFragment.kt
app/src/main/java/com/stoutner/privacybrowser/helpers/ImportExportDatabaseHelper.kt
build.gradle
gradle/wrapper/gradle-wrapper.properties

index adb26e9442800e179a66e1c55a4e70b8aba4ebb4..6c2d0240d1f09955c1d821b809067742645d1c9a 100644 (file)
@@ -28,7 +28,7 @@ android {
     compileSdk = 36
 
     defaultConfig {
-        minSdk 26
+        minSdk 29
         targetSdk 36
         versionCode 81
         versionName "3.20.1"
index df17a1c8e0de5a282a6af22d9b731363a8034698..f3e382bac1978b236cc93505efaf358de07650e2 100644 (file)
@@ -34,7 +34,7 @@
 
     <body>
         <h3><a href="https://www.stoutner.com/privacy-browser-android-3-20-1">3.20.1</a> (Code-Version 81)</h3>
-        <p>30. März 2026 - Mindest-API 26, Ziel-API 36</p>
+        <p><a href="https://gitweb.stoutner.com/?p=PrivacyBrowserAndroid.git;a=commitdiff;h=0b90f3cc8acfeb432aad1f5060f82fd9ca593a50;ds=sidebyside">30. März 2026</a> - Mindest-API 26, Ziel-API 36</p>
         <ul>
             <li>Fehler der <a href="https://redmine.stoutner.com/issues/1300">F-Droid-Builds</a> durch die Verwendung der Standard-Java-Version behoben.</li>
         </ul>
index 861a2f3c3632432b919cb97b6e37a998677c8076..45e50ef7636df326acb859e2a95b027c069240d7 100644 (file)
@@ -30,7 +30,7 @@
 
     <body>
         <h3><a href="https://www.stoutner.com/privacy-browser-android-3-20-1">3.20.1</a> (version code 81)</h3>
-        <p>30 March 2026 - minimum API 26, target API 36</p>
+        <p><a href="https://gitweb.stoutner.com/?p=PrivacyBrowserAndroid.git;a=commitdiff;h=0b90f3cc8acfeb432aad1f5060f82fd9ca593a50;ds=sidebyside">30 March 2026</a> - minimum API 26, target API 36</p>
         <ul>
             <li>Fix the <a href="https://redmine.stoutner.com/issues/1300">F-Droid builds</a> by using the default Java version.</li>
         </ul>
index 2c7063c6d89398c35a5c37551235f5046297a280..2d969ba359ba0c206de6c28d283819979401be4f 100644 (file)
@@ -32,7 +32,7 @@
 
     <body>
         <h3><a href="https://www.stoutner.com/privacy-browser-android-3-20-1">3.20.1</a> (código de versión 81)</h3>
-        <p>30 de marzo de 2026 - API mínimo 26, API objetivo 36</p>
+        <p><a href="https://gitweb.stoutner.com/?p=PrivacyBrowserAndroid.git;a=commitdiff;h=0b90f3cc8acfeb432aad1f5060f82fd9ca593a50;ds=sidebyside">30 de marzo de 2026</a> - API mínimo 26, API objetivo 36</p>
         <ul>
             <li>Corregir las <a href="https://redmine.stoutner.com/issues/1300">compilaciones de F-Droid</a> utilizando la versión predeterminada de Java.</li>
         </ul>
index a53d6612731656a4aaaf23a360af77f2e5d82a13..4d29f612c090880e388af95108492ab12939c329 100644 (file)
@@ -32,7 +32,7 @@
 
     <body>
         <h3><a href="https://www.stoutner.com/privacy-browser-android-3-20-1">3.20.1</a> (version du code 81)</h3>
-        <p>30 Mars 2026 - API minimale : 26, API optimale : 36</p>
+        <p><a href="https://gitweb.stoutner.com/?p=PrivacyBrowserAndroid.git;a=commitdiff;h=0b90f3cc8acfeb432aad1f5060f82fd9ca593a50;ds=sidebyside">30 Mars 2026</a> - API minimale : 26, API optimale : 36</p>
         <ul>
             <li>Fix the <a href="https://redmine.stoutner.com/issues/1300">F-Droid builds</a> by using the default Java version.</li>
         </ul>
index 0e21eaeb0b36b3c8d243336bd9ad6817ea28b35f..a8e486498a73cfec70572b3fc1c63bf189ea457a 100644 (file)
@@ -32,7 +32,7 @@
 
     <body>
         <h3><a href="https://www.stoutner.com/privacy-browser-android-3-20-1">3.20.1</a> (versione codice 81)</h3>
-        <p>30 Marzo 2026 - minima API 26, target API 36</p>
+        <p><a href="https://gitweb.stoutner.com/?p=PrivacyBrowserAndroid.git;a=commitdiff;h=0b90f3cc8acfeb432aad1f5060f82fd9ca593a50;ds=sidebyside">30 Marzo 2026</a> - minima API 26, target API 36</p>
         <ul>
             <li>Sistemazione della <a href="https://redmine.stoutner.com/issues/1300">compilazione per F-Droid</a> utilizzando la versione base di Java.</li>
         </ul>
index dc61105b72db50595958bd9415ed20da43883596..d9ef3853b3cc6d9c16aff49456124e115cc024b3 100644 (file)
@@ -32,7 +32,7 @@
 
     <body>
         <h3><a href="https://www.stoutner.com/privacy-browser-android-3-20-1">3.20.1</a> (código de versão 81)</h3>
-        <p>30 de março de 2026 - minimum API 26, target API 36</p>
+        <p><a href="https://gitweb.stoutner.com/?p=PrivacyBrowserAndroid.git;a=commitdiff;h=0b90f3cc8acfeb432aad1f5060f82fd9ca593a50;ds=sidebyside">30 de março de 2026</a> - minimum API 26, target API 36</p>
         <ul>
             <li>Fix the <a href="https://redmine.stoutner.com/issues/1300">F-Droid builds</a> by using the default Java version.</li>
         </ul>
index e052bb0664df847a4ac32ab38ef9a80a0f2f25f7..45abc3958df963207f1f1c91dfd221375950f247 100644 (file)
@@ -30,7 +30,7 @@
 
     <body>
         <h3><a href="https://www.stoutner.com/privacy-browser-android-3-20-1">3.20.1</a> (код версии 81)</h3>
-        <p>30 марта 2026 года - минимальный API 26, целевой API 36</p>
+        <p><a href="https://gitweb.stoutner.com/?p=PrivacyBrowserAndroid.git;a=commitdiff;h=0b90f3cc8acfeb432aad1f5060f82fd9ca593a50;ds=sidebyside">30 марта 2026 года</a> - минимальный API 26, целевой API 36</p>
         <ul>
             <li>Исправлены <a href="https://redmine.stoutner.com/issues/1300">сборки для F-Droid</a> с использованием версии Java по умолчанию.</li>
         </ul>
index 32d0defd52ba08e18964c3e7423ae7b02f96460d..d5dc62dca18985c42318cc07016c702d6711f6cd 100644 (file)
@@ -30,7 +30,7 @@
 
     <body>
         <h3><a href="https://www.stoutner.com/privacy-browser-android-3-20-1">3.20.1</a> (version code 81)</h3>
-        <p>30 Mart 2026 - minimum API 26, target API 36</p>
+        <p><a href="https://gitweb.stoutner.com/?p=PrivacyBrowserAndroid.git;a=commitdiff;h=0b90f3cc8acfeb432aad1f5060f82fd9ca593a50;ds=sidebyside">30 Mart 2026</a> - minimum API 26, target API 36</p>
         <ul>
             <li>Fix the <a href="https://redmine.stoutner.com/issues/1300">F-Droid builds</a> by using the default Java version.</li>
         </ul>
index f7c019889dec153c21d122d3677555357c6f0cdc..05094f468809ca0043e5723c9a680b9e5a0ea542 100644 (file)
@@ -32,7 +32,7 @@
 
     <body>
         <h3><a href="https://www.stoutner.com/privacy-browser-android-3-20-1">3.20.1</a> (version code 81)</h3>
-        <p>30 March 2026 - 最低支持API 26, 最高支持API 36</p>
+        <p><a href="https://gitweb.stoutner.com/?p=PrivacyBrowserAndroid.git;a=commitdiff;h=0b90f3cc8acfeb432aad1f5060f82fd9ca593a50;ds=sidebyside">30 March 2026</a> - 最低支持API 26, 最高支持API 36</p>
         <ul>
             <li>Fix the <a href="https://redmine.stoutner.com/issues/1300">F-Droid builds</a> by using the default Java version.</li>
         </ul>
index 62b46a823c285e66f61872cd18a49f724db477db..41953397932a02d162197e3f715ab83d21895e1b 100644 (file)
@@ -1,5 +1,5 @@
 /* SPDX-License-Identifier: GPL-3.0-or-later
- * SPDX-FileCopyrightText: 2018-2025 Soren Stoutner <soren@stoutner.com>
+ * SPDX-FileCopyrightText: 2018-2026 Soren Stoutner <soren@stoutner.com>
  *
  * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android/>.
  *
@@ -43,6 +43,7 @@ import androidx.appcompat.app.AppCompatActivity
 import androidx.appcompat.widget.Toolbar
 import androidx.cardview.widget.CardView
 import androidx.core.content.FileProvider
+import androidx.core.net.toUri
 import androidx.preference.PreferenceManager
 
 import com.google.android.material.snackbar.Snackbar
@@ -240,7 +241,7 @@ class ImportExportActivity : AppCompatActivity() {
         openKeychainInstalled = try {
             // If the safe call (`?.`) is null, the Elvis operator (`?"`) returns the following value instead, which is `false`.
             packageManager.getPackageInfo("org.sufficientlysecure.keychain", 0).versionName?.isNotEmpty() ?: false
-        } catch (exception: PackageManager.NameNotFoundException) {
+        } catch (_: PackageManager.NameNotFoundException) {
             // The package is not installed
             false
         }
@@ -266,7 +267,7 @@ class ImportExportActivity : AppCompatActivity() {
         // Create an array adapter for the spinner.
         val encryptionArrayAdapter = ArrayAdapter.createFromResource(this, R.array.encryption_type, R.layout.spinner_item)
 
-        // Set the drop down view resource on the spinner.
+        // Set the drop-down view resource on the spinner.
         encryptionArrayAdapter.setDropDownViewResource(R.layout.spinner_dropdown_items)
 
         // Set the array adapter for the spinner.
@@ -620,8 +621,7 @@ class ImportExportActivity : AppCompatActivity() {
                 NO_ENCRYPTION -> {
                     try {
                         // Get an input stream for the file name.
-                        // A file may be opened directly once the minimum API >= 29.  <https://developer.android.com/reference/kotlin/android/content/ContentResolver#openfile>
-                        val inputStream = contentResolver.openInputStream(Uri.parse(fileNameString))!!
+                        val inputStream = contentResolver.openInputStream(fileNameString.toUri())!!
 
                         // Import the unencrypted file.
                         importStatus = importExportDatabaseHelper.importUnencrypted(inputStream, this)
@@ -644,7 +644,7 @@ class ImportExportActivity : AppCompatActivity() {
                         val encryptionPasswordString = encryptionPasswordEditText.text.toString()
 
                         // Get an input stream for the file name.
-                        val inputStream = contentResolver.openInputStream(Uri.parse(fileNameString))!!
+                        val inputStream = contentResolver.openInputStream(fileNameString.toUri())!!
 
                         // Initialize a salt byte array.  Salt protects against rainbow table attacks.
                         val saltByteArray = ByteArray(32)
@@ -670,19 +670,19 @@ class ImportExportActivity : AppCompatActivity() {
                         // Populate the second part of the encryption password with salt byte array with the salt.
                         System.arraycopy(saltByteArray, 0, encryptionPasswordWithSaltByteArray, encryptionPasswordByteArray.size, saltByteArray.size)
 
-                        // Get a SHA-512 message digest.
+                        // Get an SHA-512 message digest.
                         val messageDigest = MessageDigest.getInstance("SHA-512")
 
                         // Hash the salted encryption password.  Otherwise, any characters after the 32nd character in the password are ignored.
                         val hashedEncryptionPasswordWithSaltByteArray = messageDigest.digest(encryptionPasswordWithSaltByteArray)
 
                         // Truncate the encryption password byte array to 256 bits (32 bytes).
-                        val truncatedHashedEncryptionPasswordWithSaltByteArray = Arrays.copyOf(hashedEncryptionPasswordWithSaltByteArray, 32)
+                        val truncatedHashedEncryptionPasswordWithSaltByteArray = hashedEncryptionPasswordWithSaltByteArray.copyOf(32)
 
                         // Create an AES secret key from the encryption password byte array.
                         val secretKey = SecretKeySpec(truncatedHashedEncryptionPasswordWithSaltByteArray, "AES")
 
-                        // Get a Advanced Encryption Standard, Galois/Counter Mode, No Padding cipher instance. Galois/Counter mode protects against modification of the ciphertext.  It doesn't use padding.
+                        // Get an Advanced Encryption Standard, Galois/Counter Mode, No Padding cipher instance. Galois/Counter mode protects against modification of the ciphertext.  It doesn't use padding.
                         val cipher = Cipher.getInstance("AES/GCM/NoPadding")
 
                         // Set the GCM tag length to be 128 bits (the maximum) and apply the initialization vector.
@@ -701,7 +701,7 @@ class ImportExportActivity : AppCompatActivity() {
                         // Create a private temporary unencrypted import file.
                         val temporaryUnencryptedImportFile = File.createTempFile("temporary_unencrypted_import_file", null, applicationContext.cacheDir)
 
-                        // Create an temporary unencrypted import file output stream.
+                        // Create a temporary unencrypted import file output stream.
                         val temporaryUnencryptedImportFileOutputStream = FileOutputStream(temporaryUnencryptedImportFile)
 
                         // Read up to 128 bits (16 bytes) of data from the cipher input stream.  `-1` will be returned when the end of the file is reached.
@@ -763,7 +763,7 @@ class ImportExportActivity : AppCompatActivity() {
                         val temporaryPgpEncryptedImportFileOutputStream = FileOutputStream(temporaryPgpEncryptedImportFile)
 
                         // Get an input stream for the file name.
-                        val inputStream = contentResolver.openInputStream(Uri.parse(fileNameString))!!
+                        val inputStream = contentResolver.openInputStream(fileNameString.toUri())!!
 
                         // Create a transfer byte array.
                         val transferByteArray = ByteArray(1024)
@@ -818,8 +818,7 @@ class ImportExportActivity : AppCompatActivity() {
 
                     try {
                         // Get the export file output stream, truncating any existing content.
-                        // A file may be opened directly once the minimum API >= 29.  <https://developer.android.com/reference/kotlin/android/content/ContentResolver#openfile>
-                        val exportFileOutputStream = contentResolver.openOutputStream(Uri.parse(noEncryptionFileNameString), "wt")!!
+                        val exportFileOutputStream = contentResolver.openOutputStream(noEncryptionFileNameString.toUri(), "wt")!!
 
                         // Export the unencrypted file.
                         val noEncryptionExportStatus = importExportDatabaseHelper.exportUnencrypted(exportFileOutputStream, this)
@@ -879,14 +878,14 @@ class ImportExportActivity : AppCompatActivity() {
                         // Populate the second part of the encryption password with salt byte array with the salt.
                         System.arraycopy(saltByteArray, 0, encryptionPasswordWithSaltByteArray, encryptionPasswordByteArray.size, saltByteArray.size)
 
-                        // Get a SHA-512 message digest.
+                        // Get an SHA-512 message digest.
                         val messageDigest = MessageDigest.getInstance("SHA-512")
 
                         // Hash the salted encryption password.  Otherwise, any characters after the 32nd character in the password are ignored.
                         val hashedEncryptionPasswordWithSaltByteArray = messageDigest.digest(encryptionPasswordWithSaltByteArray)
 
                         // Truncate the encryption password byte array to 256 bits (32 bytes).
-                        val truncatedHashedEncryptionPasswordWithSaltByteArray = Arrays.copyOf(hashedEncryptionPasswordWithSaltByteArray, 32)
+                        val truncatedHashedEncryptionPasswordWithSaltByteArray = hashedEncryptionPasswordWithSaltByteArray.copyOf(32)
 
                         // Create an AES secret key from the encryption password byte array.
                         val secretKey = SecretKeySpec(truncatedHashedEncryptionPasswordWithSaltByteArray, "AES")
@@ -897,7 +896,7 @@ class ImportExportActivity : AppCompatActivity() {
                         // Populate the initialization vector with random data.
                         secureRandom.nextBytes(initializationVector)
 
-                        // Get a Advanced Encryption Standard, Galois/Counter Mode, No Padding cipher instance. Galois/Counter mode protects against modification of the ciphertext.  It doesn't use padding.
+                        // Get an Advanced Encryption Standard, Galois/Counter Mode, No Padding cipher instance. Galois/Counter mode protects against modification of the ciphertext.  It doesn't use padding.
                         val cipher = Cipher.getInstance("AES/GCM/NoPadding")
 
                         // Set the GCM tag length to be 128 bits (the maximum) and apply the initialization vector.
@@ -910,7 +909,7 @@ class ImportExportActivity : AppCompatActivity() {
                         val passwordEncryptionFileNameString = settingsFileNameEditText.text.toString()
 
                         // Get the export file output stream, truncating any existing content.
-                        val exportFileOutputStream = contentResolver.openOutputStream(Uri.parse(passwordEncryptionFileNameString), "wt")!!
+                        val exportFileOutputStream = contentResolver.openOutputStream(passwordEncryptionFileNameString.toUri(), "wt")!!
 
                         // Add the salt and the initialization vector to the export file output stream.
                         exportFileOutputStream.write(saltByteArray)
index d430634302b8db5f1f0171df1733754eb1ee97af..b33db873e611412d951a573a8953329f29c56146 100644 (file)
@@ -1,5 +1,5 @@
 /* SPDX-License-Identifier: GPL-3.0-or-later
- * SPDX-FileCopyrightText: 2019-2025 Soren Stoutner <soren@stoutner.com>
+ * SPDX-FileCopyrightText: 2019-2026 Soren Stoutner <soren@stoutner.com>
  *
  * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android/>.
  *
@@ -312,7 +312,7 @@ class LogcatActivity : AppCompatActivity() {
 
                     // Reload the logcat.
                     populateLogcat()
-                } catch (exception: Exception) {
+                } catch (_: Exception) {
                     // Do nothing.
                 }
 
@@ -387,19 +387,19 @@ class LogcatActivity : AppCompatActivity() {
                 logcatLineString = logcatLineString!!.trim()
 
                 // Apply syntax highlighting to the logcat.
-                if (logcatLineString!!.contains("crash") || logcatLineString!!.contains("Exception") ) {  // Colorize crashes.
+                if (logcatLineString.contains("crash") || logcatLineString.contains("Exception") ) {  // Colorize crashes.
                     logcatHtmlStringBuilder.append("<strong class=\"crash\">")
                     logcatHtmlStringBuilder.append(logcatLineString)
                     logcatHtmlStringBuilder.append("</strong>")
-                } else if (logcatLineString!!.startsWith("at") || logcatLineString!!.startsWith("Process:") || logcatLineString!!.contains("FATAL")) {  // Colorize lines relating to crashes.
+                } else if (logcatLineString.startsWith("at") || logcatLineString.startsWith("Process:") || logcatLineString.contains("FATAL")) {  // Colorize lines relating to crashes.
                     logcatHtmlStringBuilder.append("<span class=\"crash\">")
                     logcatHtmlStringBuilder.append(logcatLineString)
                     logcatHtmlStringBuilder.append("</span>")
-                } else if (logcatLineString!!.startsWith("-")) {  // Colorize the headers.
+                } else if (logcatLineString.startsWith("-")) {  // Colorize the headers.
                     logcatHtmlStringBuilder.append("<span class=\"header\">")
                     logcatHtmlStringBuilder.append(logcatLineString)
                     logcatHtmlStringBuilder.append("</span>")
-                } else if (logcatLineString!!.startsWith("[ ")) {  // Colorize the time stamps.
+                } else if (logcatLineString.startsWith("[ ")) {  // Colorize the time stamps.
                     logcatHtmlStringBuilder.append("<span style=color:gray>")
                     logcatHtmlStringBuilder.append(logcatLineString)
                     logcatHtmlStringBuilder.append("</span>")
@@ -423,7 +423,7 @@ class LogcatActivity : AppCompatActivity() {
 
             // Close the buffered reader.
             logcatBufferedReader.close()
-        } catch (exception: IOException) {
+        } catch (_: IOException) {
             // Do nothing.
         }
 
index 86d778e4c8ba6bc20e72076dc033dff6f39c632d..003010b4b44433026fb96ea696a28a0dd4d79e13 100644 (file)
@@ -518,11 +518,11 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
         displayUnderCutouts = sharedPreferences.getBoolean(getString(R.string.display_under_cutouts_key), false)
 
         // Set the display under cutouts mode.  This must be done here as it doesn't appear to work correctly if handled after the app is fully initialized.
-        if ((Build.VERSION.SDK_INT < 35) && displayUnderCutouts) {
-            if (Build.VERSION.SDK_INT >= 30)
-                window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
-            else if (Build.VERSION.SDK_INT >= 28)
+        if ((Build.VERSION.SDK_INT <= 34) && displayUnderCutouts) {
+            if (Build.VERSION.SDK_INT <= 29)
                 window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
+            else
+                window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
         }
 
         // Get the entry values string arrays.
@@ -532,20 +532,10 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
         val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
 
         // Set the app theme according to the preference.  A switch statement cannot be used because the theme entry values string array is not a compile time constant.
-        if (appTheme == appThemeEntryValuesStringArray[1]) {  // The light theme is selected.
-            // Apply the light theme.
-            AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
-        } else if (appTheme == appThemeEntryValuesStringArray[2]) {  // The dark theme is selected.
-            // Apply the dark theme.
-            AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
-        } else {  // The system default theme is selected.
-            if (Build.VERSION.SDK_INT >= 28) {  // The system default theme is supported.
-                // Follow the system default theme.
-                AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
-            } else {  // The system default theme is not supported.
-                // Follow the battery saver mode.
-                AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY)
-            }
+        when (appTheme) {
+            appThemeEntryValuesStringArray[1] -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)  // The light theme is selected.
+            appThemeEntryValuesStringArray[2] -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)  // The dark theme is selected.
+            else -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)  // The system default theme is selected.
         }
 
         // Do not continue if the app theme is different than the OS theme.  The app always initially starts in the OS theme.
@@ -6191,11 +6181,6 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
         // Set the download destination.
         downloadRequest.setDestinationInExternalPublicDir(downloadDirectory, fileNameString)
 
-        // Allow media scanner to index the download if it is a media file.  This is automatic for API >= 29.
-        @Suppress("DEPRECATION")
-        if (Build.VERSION.SDK_INT <= 28)
-            downloadRequest.allowScanningByMediaScanner()
-
         // Add the URL as the description for the download.
         downloadRequest.setDescription(saveUrlString)
 
index 030acd2b9bd31da88386697d777f969ba62700e4..b1590dfcf5718cc02f925a0abb16324620666d3f 100644 (file)
@@ -1,5 +1,5 @@
 /* SPDX-License-Identifier: GPL-3.0-or-later
- * SPDX-FileCopyrightText: 2017-2024 Soren Stoutner <soren@stoutner.com>
+ * SPDX-FileCopyrightText: 2017-2024, 2026 Soren Stoutner <soren@stoutner.com>
  *
  * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android/>.
  *
@@ -23,7 +23,6 @@ import android.annotation.SuppressLint
 import android.app.Application
 import android.content.ContentResolver
 import android.graphics.Typeface
-import android.net.Uri
 import android.text.SpannableStringBuilder
 import android.text.Spanned
 import android.text.style.StyleSpan
@@ -52,6 +51,7 @@ import javax.net.ssl.SSLContext
 import javax.net.ssl.SSLSession
 import javax.net.ssl.TrustManager
 import javax.net.ssl.X509TrustManager
+import androidx.core.net.toUri
 
 
 class GetHeadersBackgroundTask {
@@ -76,7 +76,7 @@ class GetHeadersBackgroundTask {
             // Attempt to read the content data.  Return an error if this fails.
             try {
                 // Get a URI for the content URL.
-                val contentUri = Uri.parse(urlString)
+                val contentUri = urlString.toUri()
 
                 // Get a cursor with metadata about the content URL.
                 val contentCursor = contentResolver.query(contentUri, null, null, null, null)!!
@@ -350,7 +350,7 @@ class GetHeadersBackgroundTask {
 
                         // Populate the SSL certificate, returned separately.
                         sslCertificateBuilder.append(serverCertificate.toString())
-                    } catch (exception: Exception) {
+                    } catch (_: Exception) {
                         // Do nothing.
                     }
 
index cc0b19ed3252071cc6762be3c0d3922347e7199a..cc3dcf5fde51a1e2e8d5a0e29fd21d19c17879b7 100644 (file)
@@ -19,7 +19,6 @@
 
 package com.stoutner.privacybrowser.fragments
 
-import android.annotation.SuppressLint
 import android.app.Activity
 import android.app.ActivityManager
 import android.content.ClipData
@@ -536,10 +535,8 @@ class AboutVersionFragment : Fragment() {
 
         // Display the package signature.
         try {
-            // Get the first package signature.  Suppress the lint warning about the need to be careful in implementing comparison of certificates for security purposes.
-            // Once the minimum API >= 28, `GET_SIGNING_CERTIFICATES` can be used instead.  Once the minimum API >= 33, the newer `getPackageInfo()` may be used.
-            @Suppress("DEPRECATION")
-            @SuppressLint("PackageManagerGetSignatures") val packageSignature = requireContext().packageManager.getPackageInfo(requireContext().packageName,PackageManager.GET_SIGNATURES).signatures!![0]
+            // Get the first package signature.
+            val packageSignature = requireContext().packageManager.getPackageInfo(requireContext().packageName,PackageManager.GET_SIGNING_CERTIFICATES).signingInfo!!.apkContentsSigners[0]
 
             // Convert the signature to a byte array input stream.
             val certificateByteArrayInputStream: InputStream = ByteArrayInputStream(packageSignature.toByteArray())
index 14449340f4c1a709589d03a5c1352766da593647..53c9c1d18794ef7a4e38d992bc7852cdb0c8b52b 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/>.
  *
@@ -1047,14 +1047,8 @@ class SettingsFragment : PreferenceFragmentCompat() {
                             // Update the theme preference summary text.
                             appThemePreference.summary = appThemeEntriesStringArray[0]
 
-                            // Apply the new theme.
-                            if (Build.VERSION.SDK_INT >= 28) {  // The system default theme is supported.
-                                // Follow the system default theme.
-                                AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
-                            } else { // The system default theme is not supported.
-                                // Follow the battery saver mode.
-                                AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY)
-                            }
+                            // Follow the system default theme.
+                            AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
                         }
 
                         1 -> {  // The light theme is selected.
index 4b25e13464f30130ee106ac987980bef69385c60..e795940a07f68509ba1ce9a881799762a3610e9c 100644 (file)
@@ -88,11 +88,6 @@ class ImportExportDatabaseHelper {
             // Create a temporary import file.
             val temporaryImportFile = File.createTempFile("temporary_import_file", null, context.cacheDir)
 
-            // The file may be copied directly in Kotlin using `File.copyTo`.  <https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io/java.io.-file/copy-to.html>
-            // It can be copied in Android using `Files.copy` once the minimum API >= 26.
-            // <https://developer.android.com/reference/java/nio/file/Files#copy(java.nio.file.Path,%20java.nio.file.Path,%20java.nio.file.CopyOption...)>
-            // However, the file cannot be acquired from the content URI until the minimum API >= 29.  <https://developer.android.com/reference/kotlin/android/content/ContentResolver#openfile>
-
             // Create a temporary file output stream.
             val temporaryImportFileOutputStream = FileOutputStream(temporaryImportFile)
 
@@ -118,7 +113,7 @@ class ImportExportDatabaseHelper {
             // Get a handle for the shared preference.
             val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
 
-            // Open the import database.  Once the minimum API >= 27 the file can be opened directly without using the string.
+            // Open the import database.
             val importDatabase = SQLiteDatabase.openDatabase(temporaryImportFile.toString(), null, SQLiteDatabase.OPEN_READWRITE)
 
             // Get the database version.
@@ -1072,12 +1067,6 @@ class ImportExportDatabaseHelper {
             // Close the temporary export database.
             temporaryExportDatabase.close()
 
-
-            // The file may be copied directly in Kotlin using `File.copyTo`.  <https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io/java.io.-file/copy-to.html>
-            // It can be copied in Android using `Files.copy` once the minimum API >= 26.
-            // <https://developer.android.com/reference/java/nio/file/Files#copy(java.nio.file.Path,%20java.nio.file.Path,%20java.nio.file.CopyOption...)>
-            // However, the file cannot be acquired from the content URI until the minimum API >= 29.  <https://developer.android.com/reference/kotlin/android/content/ContentResolver#openfile>
-
             // Create the temporary export file input stream.
             val temporaryExportFileInputStream = FileInputStream(temporaryExportFile)
 
index 01dec7dfbea2e7ff96fb632317ec3e3d0bf8cc51..ba456754abd2d060cec0ad2f21f2703bff5ae64f 100644 (file)
@@ -26,7 +26,7 @@ buildscript {
     }
 
     dependencies {
-        classpath 'com.android.tools.build:gradle:9.1.0'
+        classpath 'com.android.tools.build:gradle:9.2.0'
         classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.10'
 
         // NOTE: Do not place your application dependencies here; they belong
index 302f29475de673e2d07b596f7b80e94703bca0b3..8c0cbb58bcb0b17f771c230fb753e54bbb38fc11 100644 (file)
@@ -20,4 +20,4 @@ distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip