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.15.0'
+ implementation 'androidx.webkit:webkit:1.16.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'
import android.app.DownloadManager
import android.app.SearchManager
import android.content.ActivityNotFoundException
-import android.content.BroadcastReceiver
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
-import android.content.IntentFilter
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.content.res.Configuration
import androidx.appcompat.content.res.AppCompatResources
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
-import androidx.core.content.ContextCompat
import androidx.core.graphics.scale
import androidx.core.net.toUri
import androidx.core.view.GravityCompat
import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog
import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog
import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog
-import com.stoutner.privacybrowser.dialogs.WaitingForProxyDialog
import com.stoutner.privacybrowser.fragments.WebViewTabFragment
import com.stoutner.privacybrowser.helpers.BLOCK_ALL_THIRD_PARTY_REQUESTS
import com.stoutner.privacybrowser.helpers.BOOKMARK_NAME
// Define the public static variables.
var currentBookmarksFolderId = 0L
val executorService = Executors.newFixedThreadPool(4)!!
- var orbotStatus = "unknown"
val pendingDialogsArrayList = ArrayList<PendingDialogDataClass>()
var proxyMode = ProxyHelper.NONE
var restartFromBookmarksActivity = false
private var navigationDrawerFirstView = true
private var objectAnimator = ObjectAnimator()
private var optionsMenu: Menu? = null
- private var orbotStatusBroadcastReceiver: BroadcastReceiver? = null
private var reapplyAppSettingsOnRestart = false
private var reapplyDomainSettingsOnRestart = false
private var sanitizeAmpRedirects = false
private var savedTabPosition = 0
private var scrollAppBar = false
private var sortBookmarksAlphabetically = false
- private var waitingForProxy = false
// Define the browse file upload activity result launcher. It must be defined before `onCreate()` is run or the app will crash.
private val browseFileUploadActivityResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult ->
if (currentWebView != null)
currentWebView!!.resumeTimers()
- // Reapply the proxy settings if the system is using a proxy. This redisplays the appropriate alert dialog.
+ // Reapply the proxy settings if the system is using a proxy.
if (proxyMode != ProxyHelper.NONE)
applyProxy(false)
}
public override fun onDestroy() {
- // Unregister the orbot status broadcast receiver if it exists.
- if (orbotStatusBroadcastReceiver != null) {
- unregisterReceiver(orbotStatusBroadcastReceiver)
- }
-
// Close the bookmarks cursor if it exists.
bookmarksCursor?.close()
// Set the proxy according to the mode.
proxyHelper.setProxy(applicationContext, appBarLayout, proxyMode)
- // Reset the waiting for proxy tracker.
- waitingForProxy = false
-
// Set the proxy.
when (proxyMode) {
ProxyHelper.NONE -> {
// Check to see if Orbot is installed.
try {
- // Get the package manager.
- val packageManager = packageManager
-
// Check to see if Orbot is in the list. This will throw an error and drop to the catch section if it isn't installed.
packageManager.getPackageInfo("org.torproject.android", 0)
-
- // Check to see if the proxy is ready.
- if (orbotStatus != ProxyHelper.ORBOT_STATUS_ON) { // Orbot is not ready.
- // Set the waiting for proxy status.
- waitingForProxy = true
-
- // Show the waiting for proxy dialog if it isn't already displayed.
- if (supportFragmentManager.findFragmentByTag(getString(R.string.waiting_for_proxy_dialog)) == null) {
- // Get a handle for the waiting for proxy alert dialog.
- val waitingForProxyDialogFragment = WaitingForProxyDialog()
-
- // Try to show the dialog. Sometimes the window is not yet active if returning from Settings.
- try {
- // Show the waiting for proxy alert dialog.
- waitingForProxyDialogFragment.show(supportFragmentManager, getString(R.string.waiting_for_proxy_dialog))
- } catch (_: Exception) {
- // Add the dialog to the pending dialog array list. It will be displayed in `onStart()`.
- pendingDialogsArrayList.add(PendingDialogDataClass(waitingForProxyDialogFragment, getString(R.string.waiting_for_proxy_dialog)))
- }
- }
- }
} catch (_: PackageManager.NameNotFoundException) { // Orbot is not installed.
// Show the Orbot not installed dialog if it is not already displayed.
if (supportFragmentManager.findFragmentByTag(getString(R.string.proxy_not_installed_dialog)) == null) {
} catch (_: PackageManager.NameNotFoundException) { // The Google Play flavor is not installed.
// Sow the I2P not installed dialog if it is not already displayed.
if (supportFragmentManager.findFragmentByTag(getString(R.string.proxy_not_installed_dialog)) == null) {
- // Get a handle for the waiting for proxy alert dialog.
+ // Get a handle for the I2P not installed alert dialog.
val i2pNotInstalledDialogFragment = ProxyNotInstalledDialog.displayDialog(proxyMode)
// Try to show the dialog. Sometimes the window is not yet active if returning from Settings.
}
}
- // Reload the WebViews if requested and not waiting for the proxy.
- if (reloadWebViews && !waitingForProxy) {
+ // Reload the WebViews if requested.
+ if (reloadWebViews) {
// Reload the WebViews.
for (i in 0 until webViewStateAdapter!!.itemCount) {
// Get the WebView tab fragment.
}
}
- // Create an Orbot status broadcast receiver.
- orbotStatusBroadcastReceiver = object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- // Get the content of the status message.
- orbotStatus = intent.getStringExtra("org.torproject.android.intent.extra.STATUS")!!
-
- // If Privacy Browser is waiting on the proxy, load the website now that Orbot is connected.
- if ((orbotStatus == ProxyHelper.ORBOT_STATUS_ON) && waitingForProxy) {
- // Reset the waiting for proxy status.
- waitingForProxy = false
-
- // Get a list of the current fragments.
- val fragmentList = supportFragmentManager.fragments
-
- // Check each fragment to see if it is a waiting for proxy dialog. Sometimes more than one is displayed.
- for (i in fragmentList.indices) {
- // Get the fragment tag.
- val fragmentTag = fragmentList[i].tag
-
- // Check to see if it is the waiting for proxy dialog.
- if (fragmentTag != null && fragmentTag == getString(R.string.waiting_for_proxy_dialog)) {
- // Dismiss the waiting for proxy dialog.
- (fragmentList[i] as DialogFragment).dismiss()
- }
- }
-
- // Reload existing URLs and load any URLs that are waiting for the proxy.
- for (i in 0 until webViewStateAdapter!!.itemCount) {
- // Get the WebView tab fragment.
- val webViewTabFragment = webViewStateAdapter!!.getPageFragment(i)
-
- // Get the fragment view.
- val fragmentView = webViewTabFragment.view
-
- // Only process the WebViews if they exist.
- if (fragmentView != null) {
- // Get the nested scroll WebView from the tab fragment.
- val nestedScrollWebView = fragmentView.findViewById<NestedScrollWebView>(R.id.nestedscroll_webview)
-
- // Get the waiting for proxy URL string.
- val waitingForProxyUrlString = nestedScrollWebView.waitingForProxyUrlString
-
- // Load the pending URL if it exists.
- if (waitingForProxyUrlString.isNotEmpty()) { // A URL is waiting to be loaded.
- // Load the URL.
- loadUrl(nestedScrollWebView, waitingForProxyUrlString)
-
- // Reset the waiting for proxy URL string.
- nestedScrollWebView.waitingForProxyUrlString = ""
- } else { // No URL is waiting to be loaded.
- // Reload the existing URL.
- nestedScrollWebView.reload()
- }
- }
- }
- }
- }
- }
-
- // Register the Orbot status broadcast receiver. `ContextCompat` must be used until the minimum API >= 34.
- ContextCompat.registerReceiver(this, orbotStatusBroadcastReceiver, IntentFilter("org.torproject.android.intent.action.STATUS"), ContextCompat.RECEIVER_EXPORTED)
-
// Get handles for views that need to be modified.
val bookmarksHeaderLinearLayout = findViewById<LinearLayout>(R.id.bookmarks_header_linearlayout)
val launchBookmarksActivityFab = findViewById<FloatingActionButton>(R.id.launch_bookmarks_activity_fab)
sharedPreferences.getString("homepage", getString(R.string.homepage_default_value))
}
- // Load the website if not waiting for the proxy.
- if (waitingForProxy) { // Store the URL to be loaded in the Nested Scroll WebView.
- nestedScrollWebView.waitingForProxyUrlString = urlToLoadString!!
- } else { // Load the URL.
- loadUrl(nestedScrollWebView, urlToLoadString!!)
- }
+ // Load the URL.
+ loadUrl(nestedScrollWebView, urlToLoadString!!)
} else { // This is not the first tab.
// Load the URL.
loadUrl(nestedScrollWebView, urlString)
+++ /dev/null
-/* SPDX-License-Identifier: GPL-3.0-or-later
- * SPDX-FileCopyrightText: 2019-2022 Soren Stoutner <soren@stoutner.com>
- *
- * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android/>.
- *
- * 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.
- *
- * 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
- * this program. If not, see <https://www.gnu.org/licenses/>.
- */
-
-package com.stoutner.privacybrowser.dialogs
-
-import android.app.Dialog
-import android.os.Bundle
-import android.view.WindowManager
-
-import androidx.appcompat.app.AlertDialog
-import androidx.fragment.app.DialogFragment
-import androidx.preference.PreferenceManager
-
-import com.stoutner.privacybrowser.R
-
-class WaitingForProxyDialog : DialogFragment() {
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- // Use a builder to create the alert dialog.
- val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
-
- // Set the layout.
- dialogBuilder.setView(R.layout.waiting_for_proxy_dialog)
-
- // Create an alert dialog from the builder.
- val alertDialog = dialogBuilder.create()
-
- // Get a handle for the shared preferences.
- val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
-
- // Get the screenshot preference.
- val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
-
- // Disable screenshots if not allowed.
- if (!allowScreenshots) {
- // Disable screenshots.
- alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
- }
-
- // Return the alert dialog.
- return alertDialog
- }
-}
\ No newline at end of file
package com.stoutner.privacybrowser.helpers
import android.content.Context
-import android.content.Intent
import android.view.View
import androidx.core.net.toUri
const val TOR = "Tor"
const val I2P = "I2P"
const val CUSTOM = "Custom"
- const val ORBOT_STATUS_ON = "ON"
}
fun setProxy(context: Context, activityView: View, proxyMode: String) {
TOR -> {
// Add the proxy to the builder. The proxy config builder can use a SOCKS proxy.
proxyConfigBuilder.addProxyRule("socks://localhost:9050")
-
- // Ask Orbot to connect if its current status is not `"ON"`.
- if (MainWebViewActivity.orbotStatus != ORBOT_STATUS_ON) {
- // Create an intent to request Orbot to start.
- val orbotIntent = Intent("org.torproject.android.intent.action.START")
-
- // Send the intent to the Orbot package.
- orbotIntent.setPackage("org.torproject.android")
-
- // Request a status response be sent back to this package.
- orbotIntent.putExtra("org.torproject.android.intent.extra.PACKAGE_NAME", context.packageName)
-
- // Make it so.
- context.sendBroadcast(orbotIntent)
- }
}
I2P -> {
/* 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/>.
*
var swipeToRefresh = false
var ultraListEnabled = true
var ultraPrivacyEnabled = true
- var waitingForProxyUrlString = ""
var webViewFragmentId: Long = 0
// Define the private variables.
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
- Copyright © 2019,2022 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.
-
- 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.
-
- 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/>. -->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:orientation="vertical" >
-
- <ProgressBar
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:layout_gravity="center_horizontal"
- android:layout_marginTop="20dp"
- android:layout_marginBottom="5dp" />
-
- <TextView
- android:id="@+id/waiting_for_proxy_textview"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:layout_gravity="center_horizontal"
- android:textAlignment="center"
- android:layout_marginTop="5dp"
- android:layout_marginBottom="20dp"
- android:text="@string/waiting_for_orbot"
- android:textSize="22sp"
- android:textStyle="bold"
- android:textColor="?android:textColorPrimary" />
-</LinearLayout>
\ No newline at end of file
<string name="orbot_not_installed_message">Der Orbot-Proxy kann erst nach Installation der Orbot-App genutzt werden.</string>
<string name="i2p_not_installed_title">I2P ist nicht installiert</string>
<string name="i2p_not_installed_message">Der I2P-Proxy kann erst nach Installation der I2P-App genutzt werden.</string>
- <string name="waiting_for_orbot">Warte auf die Verbindung zum Orbot-Proxy.</string>
<string name="custom_proxy_invalid">Die benutzerdefinierte Proxy-URL ist ungültig.</string>
<!-- About Activity. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
<string name="orbot_not_installed_message">El proxy a través de Orbot no funcionará a menos que la aplicación Orbot esté instalada.</string>
<string name="i2p_not_installed_title">I2P No Instalado</string>
<string name="i2p_not_installed_message">El proxy a través de I2P no funcionará a menos que la aplicación I2P esté instalada.</string>
- <string name="waiting_for_orbot">Esperando a que Orbot se conecte.</string>
<string name="custom_proxy_invalid">La URL del proxy personalizado no es válida.</string>
<!-- About Activity. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
<string name="orbot_not_installed_message">Le proxy via Orbot ne fonctionnera que si l\'application Orbot est installée.</string>
<string name="i2p_not_installed_title">I2P non installé</string>
<string name="i2p_not_installed_message">Le proxy via I2P ne fonctionnera que si l\'application I2P est installée.</string>
- <string name="waiting_for_orbot">En attente de la connexion d\'Orbot.</string>
<string name="custom_proxy_invalid">L\'URL du proxy personnalisé n\'est pas valide.</string>
<!-- About Activity. Android removes final spaces, but they can be manually specified with the Unicode `\u0020` formatting.
<string name="orbot_not_installed_message">Il Proxy con Orbot non funziona se non è installata la app Orbot.</string>
<string name="i2p_not_installed_title">I2P Non Installato</string>
<string name="i2p_not_installed_message">Il Proxy con I2P non funziona se non è installata la app I2P.</string>
- <string name="waiting_for_orbot">In attesa della connessione di Orbot.</string>
<string name="custom_proxy_invalid">La URL del proxy personalizzato non è valida.</string>
<!-- About Activity. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
<string name="orbot_not_installed_message">O proxy através do Orbot não funcionará a menos que o aplicativo Orbot esteja instalado.</string>
<string name="i2p_not_installed_title">I2P Não Instalado</string>
<string name="i2p_not_installed_message">O proxy através do I2P não funcionará a menos que o aplicativo I2P esteja instalado.</string>
- <string name="waiting_for_orbot">Esperando que o Orbot se conecte.</string>
<string name="custom_proxy_invalid">O URL do proxy personalizado é inválido.</string>
<!-- About Activity. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
<string name="orbot_not_installed_message">Прокси через Orbot работать не будет, если приложение Orbot не установлено.</string>
<string name="i2p_not_installed_title">I2P не установлен</string>
<string name="i2p_not_installed_message">Прокси через I2P работать не будет, если приложение I2P не установлено.</string>
- <string name="waiting_for_orbot">Ожидание подключения Orbot.</string>
<string name="custom_proxy_invalid">URL пользовательского прокси недействителен.</string>
<!-- About Activity. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
<string name="ssl_certificates">SSL Sertifikaları</string>
<string name="tracking_ids">İzleme Kimlikleri</string>
- <!-- Orbot. -->
- <string name="waiting_for_orbot">Orbot\'un bağlanması bekleniyor.</string>
-
<!-- About Activity. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
The `%*$*` code inserts variables into the displayed text and should be preserved in translation. <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
<string name="about_privacy_browser">Privacy Browser Hakkında</string>
<string name="orbot_not_installed_message">Orbot应用被安装Orbot才会工作.</string>
<string name="i2p_not_installed_title">I2P 未下载</string>
<string name="i2p_not_installed_message">I2P应用被安装I2P才会工作.</string>
- <string name="waiting_for_orbot">等待Orbot链接.</string>
<string name="custom_proxy_invalid">自定义代理无效.</string>
<!-- About Activity. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
<!-- Proxy. -->
<string name="proxy_not_installed_dialog" translatable="false">Proxy not installed dialog</string> <!-- This string is used to tag the proxy not installed dialog. It is never displayed to the user. -->
- <string name="waiting_for_proxy_dialog" translatable="false">Waiting for proxy dialog</string> <!-- This string is used to tag the waiting for proxy dialog. It is never displayed to the user. -->
<string name="orbot_not_installed_title">Orbot Not Installed</string>
<string name="orbot_not_installed_message">Proxying through Orbot will not work unless the Orbot app is installed.</string>
<string name="i2p_not_installed_title">I2P Not Installed</string>
<string name="i2p_not_installed_message">Proxying through I2P will not work unless the I2P app is installed.</string>
- <string name="waiting_for_orbot">Waiting for Orbot to connect.</string>
<string name="custom_proxy_invalid">The custom proxy URL is invalid.</string>
<!-- About Activity. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
}
dependencies {
- classpath 'com.android.tools.build:gradle:9.2.0'
+ classpath 'com.android.tools.build:gradle:9.2.1'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.10'
// NOTE: Do not place your application dependencies here; they belong