-/*
- * Copyright 2015-2024 Soren Stoutner <soren@stoutner.com>.
+/* SPDX-License-Identifier: GPL-3.0-or-later
+ * SPDX-FileCopyrightText: 2015-2025 Soren Stoutner <soren@stoutner.com>
*
* Download cookie code contributed 2017 Hendrik Knackstedt. Copyright assigned to 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/>.
*/
package com.stoutner.privacybrowser.activities
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
+import android.view.WindowInsets
+import android.view.WindowInsetsController
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import android.webkit.CookieManager
import android.webkit.HttpAuthHandler
-import android.webkit.ValueCallback
import android.webkit.SslErrorHandler
+import android.webkit.ValueCallback
import android.webkit.WebChromeClient
import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse
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.isGone
import androidx.core.view.GravityCompat
import androidx.cursoradapter.widget.CursorAdapter
import androidx.drawerlayout.widget.DrawerLayout
private lateinit var appBar: ActionBar
private lateinit var checkFilterListHelper: CheckFilterListHelper
private lateinit var bookmarksCursorAdapter: CursorAdapter
- private lateinit var bookmarksListView: ListView
private lateinit var bookmarksDrawerPinnedImageView: ImageView
+ private lateinit var bookmarksFrameLayout: FrameLayout
+ private lateinit var bookmarksHeaderLinearLayout: LinearLayout
+ private lateinit var bookmarksListView: ListView
private lateinit var bookmarksTitleTextView: TextView
+ private lateinit var browserFrameLayout: FrameLayout
private lateinit var coordinatorLayout: CoordinatorLayout
private lateinit var cookieManager: CookieManager
private lateinit var defaultFontSizeString: String
private lateinit var findOnPageLinearLayout: LinearLayout
private lateinit var fullScreenVideoFrameLayout: FrameLayout
private lateinit var initialGrayColorSpan: ForegroundColorSpan
+ private lateinit var inputMethodManager: InputMethodManager
private lateinit var navigationBackMenuItem: MenuItem
private lateinit var navigationForwardMenuItem: MenuItem
private lateinit var navigationHistoryMenuItem: MenuItem
private lateinit var navigationRequestsMenuItem: MenuItem
private lateinit var navigationScrollToBottomMenuItem: MenuItem
private lateinit var navigationView: NavigationView
+ private lateinit var oldFullScreenVideoFrameLayout: FrameLayout
private lateinit var optionsAddOrEditDomainMenuItem: MenuItem
private lateinit var optionsBlockAllThirdPartyRequestsMenuItem: MenuItem
private lateinit var optionsClearCookiesMenuItem: MenuItem
private lateinit var optionsWideViewportMenuItem: MenuItem
private lateinit var proxyHelper: ProxyHelper
private lateinit var redColorSpan: ForegroundColorSpan
- private lateinit var rootFrameLayout: FrameLayout
private lateinit var saveUrlString: String
private lateinit var searchURL: String
private lateinit var sharedPreferences: SharedPreferences
private lateinit var webViewDefaultUserAgent: String
private lateinit var webViewThemeEntryValuesStringArray: Array<String>
private lateinit var webViewViewPager2: ViewPager2
+ private lateinit var windowInsetsController: WindowInsetsController
private lateinit var ultraList: ArrayList<List<Array<String>>>
private lateinit var urlEditText: EditText
private lateinit var urlRelativeLayout: RelativeLayout
private var bookmarksDatabaseHelper: BookmarksDatabaseHelper? = null
private var bookmarksDrawerPinned = false
private var bottomAppBar = false
+ private var closeNavigationDrawer = false
private var currentWebView: NestedScrollWebView? = null
private var defaultBlockAllThirdPartyRequests = false
private var defaultCookies = false
private var defaultUltraPrivacy = true
private var defaultWideViewport = true
private var displayAdditionalAppBarIcons = false
+ private var displayUnderCutouts = false
private var displayingFullScreenVideo = false
+ private var displayingInitialTab = true
private var domainsDatabaseHelper: DomainsDatabaseHelper? = null
private var downloadWithExternalApp = false
private var fullScreenBrowsingModeEnabled = false
private var savedStateArrayList: ArrayList<Bundle>? = null
private var savedTabPosition = 0
private var scrollAppBar = false
+ private var sortBookmarksAlphabetically = false
private var ultraPrivacy: ArrayList<List<Array<String>>>? = null
private var waitingForProxy = false
- // Define the save webpage image activity result launcher. It must be defined before `onCreate()` is run or the app will crash.
+ // 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 ->
// Pass the file to the WebView.
fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(activityResult.resultCode, activityResult.data))
}
// Define the save URL activity result launcher. It must be defined before `onCreate()` is run or the app will crash.
- private val saveUrlActivityResultLauncher = registerForActivityResult<String, Uri>(ActivityResultContracts.CreateDocument("*/*")) { fileUri ->
+ private val saveUrlActivityResultLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument("*/*")) { fileUri ->
// Only save the URL if the file URI is not null, which happens if the user exited the file picker by pressing back.
if (fileUri != null) {
// Instantiate the save URL coroutine.
}
// Define the save webpage archive activity result launcher. It must be defined before `onCreate()` is run or the app will crash.
- private val saveWebpageArchiveActivityResultLauncher = registerForActivityResult<String, Uri>(ActivityResultContracts.CreateDocument("multipart/related")) { fileUri ->
+ private val saveWebpageArchiveActivityResultLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument("multipart/related")) { fileUri ->
// Only save the webpage archive if the file URI is not null, which happens if the user exited the file picker by pressing back.
if (fileUri != null) {
// Get a cursor from the content resolver.
}
// Define the save webpage image activity result launcher. It must be defined before `onCreate()` is run or the app will crash.
- private val saveWebpageImageActivityResultLauncher = registerForActivityResult<String, Uri>(ActivityResultContracts.CreateDocument("image/png")) { fileUri ->
+ private val saveWebpageImageActivityResultLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument("image/png")) { fileUri ->
// Only save the webpage image if the file URI is not null, which happens if the user exited the file picker by pressing back.
if (fileUri != null) {
// Instantiate the save webpage image coroutine.
val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
bottomAppBar = sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false)
displayAdditionalAppBarIcons = sharedPreferences.getBoolean(getString(R.string.display_additional_app_bar_icons_key), false)
- val displayUnderCutouts = sharedPreferences.getBoolean(getString(R.string.display_under_cutouts_key), false)
+ displayUnderCutouts = sharedPreferences.getBoolean(getString(R.string.display_under_cutouts_key), false)
- // Display under cutouts if specified. This must be done here as it doesn't appear to work correctly if handled after the app is fully initialized.
- if (displayUnderCutouts) {
+ // 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)
// Store the saved instance state variables. The deprecated `getParcelableArrayList` can be upgraded once the minimum API >= 33.
bookmarksDrawerPinned = savedInstanceState.getBoolean(BOOKMARKS_DRAWER_PINNED)
@Suppress("DEPRECATION")
- savedStateArrayList = savedInstanceState.getParcelableArrayList(SAVED_STATE_ARRAY_LIST)
- @Suppress("DEPRECATION")
savedNestedScrollWebViewStateArrayList = savedInstanceState.getParcelableArrayList(SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST)
- savedTabPosition = savedInstanceState.getInt(SAVED_TAB_POSITION)
savedProxyMode = savedInstanceState.getString(PROXY_MODE)
+ @Suppress("DEPRECATION")
+ savedStateArrayList = savedInstanceState.getParcelableArrayList(SAVED_STATE_ARRAY_LIST)
+ savedTabPosition = savedInstanceState.getInt(SAVED_TAB_POSITION)
}
// Enable the drawing of the entire webpage. This makes it possible to save a website image. This must be done before anything else happens with the WebView.
setContentView(R.layout.main_framelayout_top_appbar)
// Get handles for the views.
- rootFrameLayout = findViewById(R.id.root_framelayout)
+ browserFrameLayout = findViewById(R.id.browser_framelayout)
drawerLayout = findViewById(R.id.drawerlayout)
coordinatorLayout = findViewById(R.id.coordinatorlayout)
appBarLayout = findViewById(R.id.appbar_layout)
swipeRefreshLayout = findViewById(R.id.swiperefreshlayout)
webViewViewPager2 = findViewById(R.id.webview_viewpager2)
navigationView = findViewById(R.id.navigationview)
+ bookmarksFrameLayout = findViewById(R.id.bookmarks_framelayout)
+ bookmarksHeaderLinearLayout = findViewById(R.id.bookmarks_header_linearlayout)
bookmarksListView = findViewById(R.id.bookmarks_drawer_listview)
bookmarksTitleTextView = findViewById(R.id.bookmarks_title_textview)
bookmarksDrawerPinnedImageView = findViewById(R.id.bookmarks_drawer_pinned_imageview)
+ oldFullScreenVideoFrameLayout = findViewById(R.id.old_full_screen_video_framelayout)
fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout)
+ // Get a handle for the window inset controller.
+ if (Build.VERSION.SDK_INT >= 30)
+ windowInsetsController = browserFrameLayout.windowInsetsController!!
+
+ // Set the layout to fit the system windows according to the API.
+ if (Build.VERSION.SDK_INT >= 35) {
+ // Set the browser frame layout to fit system windows.
+ browserFrameLayout.fitsSystemWindows = true
+ } else {
+ // Set the layouts to fit system windows.
+ coordinatorLayout.fitsSystemWindows = true
+ bookmarksFrameLayout.fitsSystemWindows = true
+ }
+
// Get a handle for the navigation menu.
val navigationMenu = navigationView.menu
urlRelativeLayout = findViewById(R.id.url_relativelayout)
urlEditText = findViewById(R.id.url_edittext)
+ // Store the URL when it is changed. This enables the restoring of partially-typed URLs when tabs change.
+ urlEditText.addTextChangedListener(object : TextWatcher {
+ override fun beforeTextChanged(charSequence: CharSequence, start: Int, count: Int, after: Int) {
+ // Do nothing.
+ }
+
+ override fun onTextChanged(charSequence: CharSequence, start: Int, before: Int, count: Int) {
+ // Do nothing.
+ }
+
+ override fun afterTextChanged(editable: Editable) {
+ // Store the URL
+ currentWebView?.currentUrl = editable.toString()
+ }
+ })
+
// Initially disable the sliding drawers. They will be enabled once the filter lists are loaded.
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
if (proxyMode != ProxyHelper.NONE)
applyProxy(false)
- // Reapply any system UI flags.
- if (displayingFullScreenVideo || inFullScreenBrowsingMode) { // The system is displaying a website or a video in full screen mode.
+ // Reapply any system UI flags on older APIs.
+ if ((Build.VERSION.SDK_INT < 30) && (displayingFullScreenVideo || inFullScreenBrowsingMode)) { // The system is displaying a website or a video in full screen mode.
/* Hide the system bars.
* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
* SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
* SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
*/
- // The deprecated command can be switched to `WindowInsetsController` once the minimum API >= 30.
@Suppress("DEPRECATION")
- rootFrameLayout.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+ browserFrameLayout.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
}
// Show any pending dialogs.
pendingDialogsArrayList.clear()
}
- public override fun onSaveInstanceState(savedInstanceState: Bundle) {
+ public override fun onSaveInstanceState(outState: Bundle) {
// Run the default commands.
- super.onSaveInstanceState(savedInstanceState)
+ super.onSaveInstanceState(outState)
// Only save the instance state if the WebView state adapter is not null, which will be the case if the app is restarting to change the initial app theme.
if (webViewStateAdapter != null) {
// Initialize the saved state array lists.
- savedStateArrayList = ArrayList<Bundle>()
- savedNestedScrollWebViewStateArrayList = ArrayList<Bundle>()
+ savedStateArrayList = ArrayList()
+ savedNestedScrollWebViewStateArrayList = ArrayList()
// Get the URLs from each tab.
for (i in 0 until webViewStateAdapter!!.itemCount) {
val currentTabPosition = tabLayout.selectedTabPosition
// Store the saved states in the bundle.
- savedInstanceState.putBoolean(BOOKMARKS_DRAWER_PINNED, bookmarksDrawerPinned)
- savedInstanceState.putString(PROXY_MODE, proxyMode)
- savedInstanceState.putParcelableArrayList(SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST, savedNestedScrollWebViewStateArrayList)
- savedInstanceState.putParcelableArrayList(SAVED_STATE_ARRAY_LIST, savedStateArrayList)
- savedInstanceState.putInt(SAVED_TAB_POSITION, currentTabPosition)
+ outState.putBoolean(BOOKMARKS_DRAWER_PINNED, bookmarksDrawerPinned)
+ outState.putString(PROXY_MODE, proxyMode)
+ outState.putParcelableArrayList(SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST, savedNestedScrollWebViewStateArrayList)
+ outState.putParcelableArrayList(SAVED_STATE_ARRAY_LIST, savedStateArrayList)
+ outState.putInt(SAVED_TAB_POSITION, currentTabPosition)
}
}
val privateDataDirectoryString = applicationInfo.dataDir
// Get the storage directories.
- val localStorageDirectory = File("$privateDataDirectoryString/app_webview/Local Storage/")
- val indexedDBDirectory = File("$privateDataDirectoryString/app_webview/IndexedDB")
+ val localStorageDirectory = File("$privateDataDirectoryString/app_webview/Default/Local Storage/")
+ val sessionStorageDirectory = File("$privateDataDirectoryString/app_webview/Default/Session Storage/")
+ val indexedDBDirectory = File("$privateDataDirectoryString/app_webview/Default/IndexedDB")
// Initialize the number of files counters.
var localStorageDirectoryNumberOfFiles = 0
+ var sessionStorageDirectoryNumberOfFiles = 0
var indexedDBDirectoryNumberOfFiles = 0
// Get a count of the number of files in the Local Storage directory. The list can be null, in which case a `0` is returned.
if (localStorageDirectory.exists())
localStorageDirectoryNumberOfFiles = (localStorageDirectory.list())?.size ?: 0
+ // Get a count of the number of files in the Local Storage directory. The list can be null, in which case a `0` is returned.
+ if (sessionStorageDirectory.exists())
+ sessionStorageDirectoryNumberOfFiles = (sessionStorageDirectory.list())?.size ?: 0
+
// Get a count of the number of files in the IndexedDB directory. The list can be null, in which case a `0` is returned.
if (indexedDBDirectory.exists())
indexedDBDirectoryNumberOfFiles = (indexedDBDirectory.list())?.size ?: 0
// Enable Clear DOM Storage if there is any.
- optionsClearDomStorageMenuItem.isEnabled = localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0
+ optionsClearDomStorageMenuItem.isEnabled = localStorageDirectoryNumberOfFiles > 0 || sessionStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0
// Enable Clear Data if any of the submenu items are enabled.
optionsClearDataMenuItem.isEnabled = (optionsClearCookiesMenuItem.isEnabled || optionsClearDomStorageMenuItem.isEnabled)
.addCallback(object : Snackbar.Callback() {
override fun onDismissed(snackbar: Snackbar, event: Int) {
if (event != DISMISS_EVENT_ACTION) { // The snackbar was dismissed without the undo button being pushed.
- // Get a handle for the web storage.
- val webStorage = WebStorage.getInstance()
-
- // Delete the DOM Storage.
- webStorage.deleteAllData()
+ // Ask web storage to clear the DOM storage.
+ WebStorage.getInstance().deleteAllData()
// Initialize a handler to manually delete the DOM storage files and directories.
val deleteDomStorageHandler = Handler(Looper.getMainLooper())
val privateDataDirectoryString = applicationInfo.dataDir
// A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
- val deleteLocalStorageProcess = runtime.exec(arrayOf("rm", "-rf", "$privateDataDirectoryString/app_webview/Local Storage/"))
+ val deleteLocalStorageProcess = runtime.exec(arrayOf("rm", "-rf", "$privateDataDirectoryString/app_webview/Default/Local Storage/"))
+ val deleteSessionStorageProcess = runtime.exec(arrayOf("rm", "-rf", "$privateDataDirectoryString/app_webview/Default/Session Storage/"))
+ val deleteWebDataProcess = runtime.exec(arrayOf("rm", "-rf", "$privateDataDirectoryString/app_webview/Default/Web Data"))
+ val deleteWebDataJournalProcess = runtime.exec(arrayOf("rm", "-rf", "$privateDataDirectoryString/app_webview/Default/Web Data-journal"))
// Multiple commands must be used because `Runtime.exec()` does not like `*`.
- val deleteIndexProcess = runtime.exec("rm -rf $privateDataDirectoryString/app_webview/IndexedDB")
- val deleteQuotaManagerProcess = runtime.exec("rm -f $privateDataDirectoryString/app_webview/QuotaManager")
- val deleteQuotaManagerJournalProcess = runtime.exec("rm -f $privateDataDirectoryString/app_webview/QuotaManager-journal")
- val deleteDatabasesProcess = runtime.exec("rm -rf $privateDataDirectoryString/app_webview/databases")
+ val deleteIndexProcess = runtime.exec("rm -rf $privateDataDirectoryString/app_webview/Default/IndexedDB")
+ val deleteWebStorageProcess = runtime.exec("rm -rf $privateDataDirectoryString/app_webview/Default/WebStorage")
+ val deleteDatabasesProcess = runtime.exec("rm -rf $privateDataDirectoryString/app_webview/Default/databases")
+ val deleteBlobStorageProcess = runtime.exec("rm -rf $privateDataDirectoryString/app_webview/Default/blob_storage")
// Wait for the processes to finish.
deleteLocalStorageProcess.waitFor()
+ deleteSessionStorageProcess.waitFor()
+ deleteWebDataProcess.waitFor()
+ deleteWebDataJournalProcess.waitFor()
deleteIndexProcess.waitFor()
- deleteQuotaManagerProcess.waitFor()
- deleteQuotaManagerJournalProcess.waitFor()
+ deleteWebStorageProcess.waitFor()
deleteDatabasesProcess.waitFor()
+ deleteBlobStorageProcess.waitFor()
} catch (exception: Exception) {
// Do nothing if an error is thrown.
}
// Set the focus on the find on page edit text.
findOnPageEditText.requestFocus()
- // Get a handle for the input method manager.
- val inputMethodManager = (getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager)
-
// Display the keyboard. `0` sets no input flags.
inputMethodManager.showSoftInput(findOnPageEditText, 0)
}, 200)
startActivity(domainsIntent)
} else { // Add a new domain.
// Get the current URI.
- val currentUri = Uri.parse(currentWebView!!.url)
+ val currentUri = currentWebView!!.url!!.toUri()
// Get the current domain from the URI. Use an empty string if it is null.
val currentDomain = currentUri.host?: ""
val genericFileManagerIntent = Intent(Intent.ACTION_VIEW)
// Open the download directory.
- genericFileManagerIntent.setDataAndType(Uri.parse(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString()), DocumentsContract.Document.MIME_TYPE_DIR)
+ genericFileManagerIntent.setDataAndType(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString().toUri(), DocumentsContract.Document.MIME_TYPE_DIR)
// Launch as a new task so that the file manager and Privacy Browser show as separate windows in the recent tasks list.
genericFileManagerIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
val alternateFileManagerIntent = Intent(Intent.ACTION_VIEW)
// Open the download directory.
- alternateFileManagerIntent.setDataAndType(Uri.parse(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString()), "resource/folder")
+ alternateFileManagerIntent.setDataAndType(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString().toUri(), "resource/folder")
// Launch as a new task so that the file manager and Privacy Browser show as separate windows in the recent tasks list.
alternateFileManagerIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
// Set the target URL as the context menu title.
contextMenu.setHeaderTitle(linkUrl)
+ // Get a new message from the WebView handler.
+ val hrefMessage = currentWebView!!.handler.obtainMessage()
+
+ // Request the focus node href.
+ currentWebView!!.requestFocusNodeHref(hrefMessage)
+
+ // Get the link text from the href message.
+ val linkText = hrefMessage.data.getString("title")
+
// Add an open in new tab entry.
contextMenu.add(R.string.open_in_new_tab).setOnMenuItemClickListener {
// Load the link URL in a new tab and move to it.
true
}
+ // Add a Copy Text entry if the link text is not null.
+ if (linkText != null) {
+ contextMenu.add(R.string.copy_text).setOnMenuItemClickListener {
+ // Save the link URL in a clip data.
+ val srcAnchorTypeTextClipData = ClipData.newPlainText(getString(R.string.copy_text), linkText)
+
+ // Set the clip data as the clipboard's primary clip.
+ clipboardManager.setPrimaryClip(srcAnchorTypeTextClipData)
+
+ // Consume the event.
+ true
+ }
+ }
+
// Add an empty cancel entry, which by default closes the context menu.
contextMenu.add(R.string.cancel)
}
// Set the target URL as the title of the context menu.
contextMenu.setHeaderTitle(linkUrl)
+ // Get a new message from the WebView handler.
+ val hrefMessage = currentWebView!!.handler.obtainMessage()
+
+ // Request the focus node href.
+ currentWebView!!.requestFocusNodeHref(hrefMessage)
+
+ // Get the link text from the href message.
+ val linkText = hrefMessage.data.getString("title")
+
// Add a write email entry.
contextMenu.add(R.string.write_email).setOnMenuItemClickListener {
// Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
val emailIntent = Intent(Intent.ACTION_SENDTO)
// Parse the url and set it as the data for the intent.
- emailIntent.data = Uri.parse("mailto:$linkUrl")
+ emailIntent.data = "mailto:$linkUrl".toUri()
// `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
emailIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
true
}
+ // Add a Copy Text entry if the link text is not null.
+ if (linkText != null) {
+ contextMenu.add(R.string.copy_text).setOnMenuItemClickListener {
+ // Save the link URL in a clip data.
+ val srcEmailTypeTextClipData = ClipData.newPlainText(getString(R.string.copy_text), linkText)
+
+ // Set the clip data as the clipboard's primary clip.
+ clipboardManager.setPrimaryClip(srcEmailTypeTextClipData)
+
+ // Consume the event.
+ true
+ }
+ }
+
// Add an empty cancel entry, which by default closes the context menu.
contextMenu.add(R.string.cancel)
}
proxyMode = sharedPreferences.getString(getString(R.string.proxy_key), getString(R.string.proxy_default_value))!!
fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean(getString(R.string.full_screen_browsing_mode_key), false)
hideAppBar = sharedPreferences.getBoolean(getString(R.string.hide_app_bar_key), true)
+ displayUnderCutouts = sharedPreferences.getBoolean(getString(R.string.display_under_cutouts_key), false)
val downloadProvider = sharedPreferences.getString(getString(R.string.download_provider_key), getString(R.string.download_provider_default_value))!!
scrollAppBar = sharedPreferences.getBoolean(getString(R.string.scroll_app_bar_key), false)
+ sortBookmarksAlphabetically = sharedPreferences.getBoolean(getString(R.string.sort_bookmarks_alphabetically_key), false)
// Determine if downloading should be handled by an external app.
downloadWithExternalApp = (downloadProvider == downloadProviderEntryValuesStringArray[2])
}
}
+ // Force an exit of full screen browsing mode if it is now disabled in settings.
+ if (!fullScreenBrowsingModeEnabled)
+ inFullScreenBrowsingMode = false
+
// Update the full screen browsing mode settings.
- if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
+ if (inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
// Update the visibility of the app bar, which might have changed in the settings.
if (hideAppBar) {
// Hide the tab linear layout.
appBar.show()
}
- /* Hide the system bars.
- * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
- * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
- * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
- * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
- */
+ // Re-enforce the fullscreen flags if the API < 30.
+ if (Build.VERSION.SDK_INT < 30) {
+ /* Hide the system bars.
+ * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+ * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+ * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+ * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+ */
- // The deprecated command can be switched to `WindowInsetsController` once the minimum API >= 30.
- @Suppress("DEPRECATION")
- rootFrameLayout.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+ @Suppress("DEPRECATION")
+ window.addFlags(View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
+ }
} else { // Privacy Browser is not in full screen browsing mode.
- // Reset the full screen tracker, which could be true if Privacy Browser was in full screen mode before entering settings and full screen browsing was disabled.
- inFullScreenBrowsingMode = false
-
// Show the tab linear layout.
tabsLinearLayout.visibility = View.VISIBLE
// Show the app bar.
appBar.show()
- // Remove the `SYSTEM_UI` flags from the root frame layout. The deprecated command can be switched to `WindowInsetsController` once the minimum API >= 30.
- @Suppress("DEPRECATION")
- rootFrameLayout.systemUiVisibility = 0
+ // Remove the fullscreen flags if the API < 30.
+ if (Build.VERSION.SDK_INT < 30) {
+ /* Show the system bars.
+ * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+ * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+ * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+ * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+ */
+
+ @Suppress("DEPRECATION")
+ window.clearFlags(View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
+ }
}
+
+ // Load the bookmarks folder.
+ loadBookmarksFolder()
}
// `reloadWebsite` is used if returning from the Domains activity. Otherwise JavaScript might not function correctly if it is newly enabled.
nestedScrollWebView.currentUrl = url!!
// Parse the URL into a URI.
- val uri = Uri.parse(url)
+ val uri = url.toUri()
// Extract the domain from the URI.
var newHostName = uri.host
nestedScrollWebView.previousWebpageTitle = tabTitleTextView.text.toString()
// Set the default favorite icon as the favorite icon for this tab.
- tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(nestedScrollWebView.getFavoriteIcon(), 64, 64, true))
+ tabFavoriteIconImageView.setImageBitmap(nestedScrollWebView.getFavoriteIcon().scale(128, 128))
// Set the loading title text.
tabTitleTextView.setText(R.string.loading)
// Get the color background int from the typed value.
val colorBackgroundInt = colorBackgroundTypedValue.data
- // Set the default app bar layout background.
+ // Set the default app bar and status bar backgrounds.
appBarLayout.setBackgroundColor(colorBackgroundInt)
+
+ // Set the background color if the API < 35 (Android 15). https://redmine.stoutner.com/issues/1169
+ @Suppress("DEPRECATION")
+ if (Build.VERSION.SDK_INT < 35)
+ window.statusBarColor = colorBackgroundInt
}
ProxyHelper.TOR -> {
- // Set the app bar background to indicate proxying is enabled.
+ // Set the app bar and status bar backgrounds to indicate proxying is enabled.
appBarLayout.setBackgroundResource(R.color.blue_background)
+ // Set the background color if the API < 35 (Android 15). https://redmine.stoutner.com/issues/1169
+ @Suppress("DEPRECATION")
+ if (Build.VERSION.SDK_INT < 35)
+ window.statusBarColor = getColor(R.color.blue_background)
+
// Check to see if Orbot is installed.
try {
// Get the package manager.
}
ProxyHelper.I2P -> {
- // Set the app bar background to indicate proxying is enabled.
+ // Set the app bar and status bar backgrounds to indicate proxying is enabled.
appBarLayout.setBackgroundResource(R.color.blue_background)
+ // Set the background color if the API < 35 (Android 15). https://redmine.stoutner.com/issues/1169
+ @Suppress("DEPRECATION")
+ if (Build.VERSION.SDK_INT < 35)
+ window.statusBarColor = getColor(R.color.blue_background)
+
// Check to see if I2P is installed.
try {
// Check to see if the F-Droid flavor is installed. This will throw an error and drop to the catch section if it isn't installed.
}
}
- ProxyHelper.CUSTOM ->
- // Set the app bar background to indicate proxying is enabled.
+ ProxyHelper.CUSTOM -> {
+ // Set the app bar and status bar backgrounds to indicate proxying is enabled.
appBarLayout.setBackgroundResource(R.color.blue_background)
+
+ // Set the background color if the API < 35 (Android 15). https://redmine.stoutner.com/issues/1169
+ @Suppress("DEPRECATION")
+ if (Build.VERSION.SDK_INT < 35)
+ window.statusBarColor = getColor(R.color.blue_background)
+ }
}
// Reload the WebViews if requested and not waiting for the proxy.
// Manually delete the DOM storage files and directories, as web storage sometimes will not flush its changes to disk before system exit is run.
try {
// A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
- val deleteLocalStorageProcess = runtime.exec(arrayOf("rm", "-rf", "$privateDataDirectoryString/app_webview/Local Storage/"))
+ val deleteLocalStorageProcess = runtime.exec(arrayOf("rm", "-rf", "$privateDataDirectoryString/app_webview/Default/Local Storage/"))
+ val deleteSessionStorageProcess = runtime.exec(arrayOf("rm", "-rf", "$privateDataDirectoryString/app_webview/Default/Session Storage/"))
+ val deleteWebDataProcess = runtime.exec(arrayOf("rm", "-rf", "$privateDataDirectoryString/app_webview/Default/Web Data"))
+ val deleteWebDataJournalProcess = runtime.exec(arrayOf("rm", "-rf", "$privateDataDirectoryString/app_webview/Default/Web Data-journal"))
// Multiple commands must be used because `Runtime.exec()` does not like `*`.
- val deleteIndexProcess = runtime.exec("rm -rf $privateDataDirectoryString/app_webview/IndexedDB")
- val deleteQuotaManagerProcess = runtime.exec("rm -f $privateDataDirectoryString/app_webview/QuotaManager")
- val deleteQuotaManagerJournalProcess = runtime.exec("rm -f $privateDataDirectoryString/app_webview/QuotaManager-journal")
- val deleteDatabaseProcess = runtime.exec("rm -rf $privateDataDirectoryString/app_webview/databases")
+ val deleteIndexProcess = runtime.exec("rm -rf $privateDataDirectoryString/app_webview/Default/IndexedDB")
+ val deleteWebStorageProcess = runtime.exec("rm -rf $privateDataDirectoryString/app_webview/Default/WebStorage")
+ val deleteDatabasesProcess = runtime.exec("rm -rf $privateDataDirectoryString/app_webview/Default/databases")
+ val deleteBlobStorageProcess = runtime.exec("rm -rf $privateDataDirectoryString/app_webview/Default/blob_storage")
// Wait until the processes have finished.
deleteLocalStorageProcess.waitFor()
+ deleteSessionStorageProcess.waitFor()
+ deleteWebDataProcess.waitFor()
+ deleteWebDataJournalProcess.waitFor()
deleteIndexProcess.waitFor()
- deleteQuotaManagerProcess.waitFor()
- deleteQuotaManagerJournalProcess.waitFor()
- deleteDatabaseProcess.waitFor()
+ deleteWebStorageProcess.waitFor()
+ deleteDatabasesProcess.waitFor()
+ deleteBlobStorageProcess.waitFor()
} catch (exception: Exception) {
// Do nothing if an error is thrown.
}
// Show the toolbar.
toolbar.visibility = View.VISIBLE
- // Get a handle for the input method manager.
- val inputMethodManager = (getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager)
-
// Hide the keyboard.
inputMethodManager.hideSoftInputFromWindow(toolbar.windowToken, 0)
}
}
}
- override fun createBookmark(dialogFragment: DialogFragment, favoriteIconBitmap: Bitmap) {
+ override fun createBookmark(dialogFragment: DialogFragment) {
// Get the dialog.
val dialog = dialogFragment.dialog!!
// Get the views from the dialog fragment.
- val createBookmarkNameEditText = dialog.findViewById<EditText>(R.id.create_bookmark_name_edittext)
- val createBookmarkUrlEditText = dialog.findViewById<EditText>(R.id.create_bookmark_url_edittext)
+ val webpageFavoriteIconRadioButton = dialog.findViewById<RadioButton>(R.id.webpage_favorite_icon_radiobutton)
+ val webpageFavoriteIconImageView = dialog.findViewById<ImageView>(R.id.webpage_favorite_icon_imageview)
+ val customIconImageView = dialog.findViewById<ImageView>(R.id.custom_icon_imageview)
+ val bookmarkNameEditText = dialog.findViewById<EditText>(R.id.bookmark_name_edittext)
+ val bookmarkUrlEditText = dialog.findViewById<EditText>(R.id.bookmark_url_edittext)
// Extract the strings from the edit texts.
- val bookmarkNameString = createBookmarkNameEditText.text.toString()
- val bookmarkUrlString = createBookmarkUrlEditText.text.toString()
+ val bookmarkNameString = bookmarkNameEditText.text.toString()
+ val bookmarkUrlString = bookmarkUrlEditText.text.toString()
+
+ // Get the selected favorite icon drawable.
+ val favoriteIconDrawable = if (webpageFavoriteIconRadioButton.isChecked) // Use the webpage favorite icon.
+ webpageFavoriteIconImageView.drawable
+ else // Use the custom icon.
+ customIconImageView.drawable
+
+ // Cast the favorite icon bitmap to a bitmap drawable
+ val favoriteIconBitmapDrawable = favoriteIconDrawable as BitmapDrawable
+
+ // Convert the favorite icon bitmap drawable to a bitmap.
+ val favoriteIconBitmap = favoriteIconBitmapDrawable.bitmap
// Create a favorite icon byte array output stream.
val favoriteIconByteArrayOutputStream = ByteArrayOutputStream()
bookmarksDatabaseHelper!!.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolderId, newBookmarkDisplayOrder, favoriteIconByteArray)
// Update the bookmarks cursor with the current contents of this folder.
- bookmarksCursor = bookmarksDatabaseHelper!!.getBookmarksByDisplayOrder(currentBookmarksFolderId)
+ bookmarksCursor = if (sortBookmarksAlphabetically)
+ bookmarksDatabaseHelper!!.getBookmarksSortedAlphabetically(currentBookmarksFolderId)
+ else
+ bookmarksDatabaseHelper!!.getBookmarksByDisplayOrder(currentBookmarksFolderId)
// Update the list view.
bookmarksCursorAdapter.changeCursor(bookmarksCursor)
bookmarksListView.setSelection(newBookmarkDisplayOrder)
}
- override fun createBookmarkFolder(dialogFragment: DialogFragment, favoriteIconBitmap: Bitmap) {
+ override fun createBookmarkFolder(dialogFragment: DialogFragment) {
// Get the dialog.
val dialog = dialogFragment.dialog!!
// Get handles for the views in the dialog fragment.
+ val defaultFolderIconRadioButton = dialog.findViewById<RadioButton>(R.id.default_folder_icon_radiobutton)
+ val defaultFolderIconImageView = dialog.findViewById<ImageView>(R.id.default_folder_icon_imageview)
+ val webpageFavoriteIconRadioButton = dialog.findViewById<RadioButton>(R.id.webpage_favorite_icon_radiobutton)
+ val webpageFavoriteIconImageView = dialog.findViewById<ImageView>(R.id.webpage_favorite_icon_imageview)
+ val customIconImageView = dialog.findViewById<ImageView>(R.id.custom_icon_imageview)
val folderNameEditText = dialog.findViewById<EditText>(R.id.folder_name_edittext)
- val defaultIconRadioButton = dialog.findViewById<RadioButton>(R.id.default_icon_radiobutton)
- val defaultIconImageView = dialog.findViewById<ImageView>(R.id.default_icon_imageview)
// Get new folder name string.
val folderNameString = folderNameEditText.text.toString()
// Set the folder icon bitmap according to the dialog.
- val folderIconBitmap: Bitmap = if (defaultIconRadioButton.isChecked) { // Use the default folder icon.
- // Get the default folder icon drawable.
- val folderIconDrawable = defaultIconImageView.drawable
-
- // Convert the folder icon drawable to a bitmap drawable.
- val folderIconBitmapDrawable = folderIconDrawable as BitmapDrawable
-
- // Convert the folder icon bitmap drawable to a bitmap.
- folderIconBitmapDrawable.bitmap
- } else { // Use the WebView favorite icon.
- // Copy the favorite icon bitmap to the folder icon bitmap.
- favoriteIconBitmap
- }
+ val folderIconDrawable = if (defaultFolderIconRadioButton.isChecked) // Use the default folder icon.
+ defaultFolderIconImageView.drawable
+ else if (webpageFavoriteIconRadioButton.isChecked) // Use the webpage favorite icon.
+ webpageFavoriteIconImageView.drawable
+ else // Use the custom icon.
+ customIconImageView.drawable
+
+ // Cast the folder icon bitmap to a bitmap drawable.
+ val folderIconBitmapDrawable = folderIconDrawable as BitmapDrawable
+
+ // Convert the folder icon bitmap drawable to a bitmap.
+ val folderIconBitmap = folderIconBitmapDrawable.bitmap
// Create a folder icon byte array output stream.
val folderIconByteArrayOutputStream = ByteArrayOutputStream()
bookmarksDatabaseHelper!!.createFolder(folderNameString, currentBookmarksFolderId, displayOrder = 0, folderIconByteArray)
// Update the bookmarks cursor with the current contents of this folder.
- bookmarksCursor = bookmarksDatabaseHelper!!.getBookmarksByDisplayOrder(currentBookmarksFolderId)
+ bookmarksCursor = if (sortBookmarksAlphabetically)
+ bookmarksDatabaseHelper!!.getBookmarksSortedAlphabetically(currentBookmarksFolderId)
+ else
+ bookmarksDatabaseHelper!!.getBookmarksByDisplayOrder(currentBookmarksFolderId)
// Update the list view.
bookmarksCursorAdapter.changeCursor(bookmarksCursor)
}
private fun exitFullScreenVideo() {
- // Re-enable the screen timeout.
- fullScreenVideoFrameLayout.keepScreenOn = false
-
// Unset the full screen video flag.
displayingFullScreenVideo = false
- // Remove all the views from the full screen video frame layout.
- fullScreenVideoFrameLayout.removeAllViews()
+ // Hide the full screen video according to the API and display under cutouts status.
+ if ((Build.VERSION.SDK_INT < 35) && displayUnderCutouts) { // The device is running API < 35 and display under cutouts is enabled.
+ // Re-enable the screen timeout.
+ oldFullScreenVideoFrameLayout.keepScreenOn = false
+
+ // Remove all the views from the full screen video frame layout.
+ oldFullScreenVideoFrameLayout.removeAllViews()
- // Hide the full screen video frame layout.
- fullScreenVideoFrameLayout.visibility = View.GONE
+ // Hide the full screen video frame layout.
+ oldFullScreenVideoFrameLayout.visibility = View.GONE
+ } else { // The device is running API >= 35 or display under cutouts is disabled.
+ // Re-enable the screen timeout.
+ fullScreenVideoFrameLayout.keepScreenOn = false
+
+ // Remove all the views from the full screen video frame layout.
+ fullScreenVideoFrameLayout.removeAllViews()
+
+ // Hide the full screen video frame layout.
+ fullScreenVideoFrameLayout.visibility = View.GONE
+ }
// Enable the sliding drawers.
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
- // Show the coordinator layout.
- coordinatorLayout.visibility = View.VISIBLE
+ // Show the browser view according to the API and display under cutouts status.
+ if ((Build.VERSION.SDK_INT < 35) && displayUnderCutouts) { // The device is running API < 35 and display under cutouts is enabled.
+ // Display the app bar if it is not supposed to be hidden, the `||` ensures that `!hideAppBar` is only evaluated in `inFullScreenBrowsingMode == true`.
+ if (!inFullScreenBrowsingMode || !hideAppBar) {
+ // Show the tab linear layout.
+ tabsLinearLayout.visibility = View.VISIBLE
+
+ // Show the app bar.
+ appBar.show()
+ }
+
+ // Display the swipe refresh layout (which includes the WebView).
+ swipeRefreshLayout.visibility = View.VISIBLE
+
+ // Enable fits system windows if not in full screen browsing mode.
+ if (!inFullScreenBrowsingMode) {
+ coordinatorLayout.fitsSystemWindows = true
+ bookmarksFrameLayout.fitsSystemWindows = true
+ }
+ } else { // The device is running API >= 35 or display under cutouts is disabled.
+ browserFrameLayout.visibility = View.VISIBLE
+ }
// Apply the appropriate full screen mode flags.
- if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
+ if (inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
// Hide the app bar if specified.
if (hideAppBar) {
// Hide the tab linear layout.
appBar.hide()
}
- /* Hide the system bars.
- * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
- * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
- * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
- * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
- */
+ if (Build.VERSION.SDK_INT < 30) {
+ /* Hide the system bars.
+ * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+ * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+ * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+ * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+ */
- // The deprecated command can be switched to `WindowInsetsController` once the minimum API >= 30.
- @Suppress("DEPRECATION")
- rootFrameLayout.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+ @Suppress("DEPRECATION")
+ window.addFlags(View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
+ }
} else { // Switch to normal viewing mode.
- // Remove the `SYSTEM_UI` flags from the root frame layout. The deprecated command can be switched to `WindowInsetsController` once the minimum API >= 30.
- @Suppress("DEPRECATION")
- rootFrameLayout.systemUiVisibility = 0
+ // Show the system bars according to the API.
+ if (Build.VERSION.SDK_INT >= 30) {
+ // Show the system bars.
+ windowInsetsController.show(WindowInsets.Type.systemBars())
+ } else {
+ /* Show the system bars.
+ * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+ * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+ * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+ * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+ */
+
+ @Suppress("DEPRECATION")
+ window.clearFlags(View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
+ }
}
}
@SuppressLint("ClickableViewAccessibility")
private fun initializeApp() {
// Get a handle for the input method.
- val inputMethodManager = (getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager)
+ inputMethodManager = (getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager)
// Initialize the color spans for highlighting the URLs.
initialGrayColorSpan = ForegroundColorSpan(getColor(R.color.gray_500))
// Search for the string on the page whenever a character changes in the find on page edit text.
findOnPageEditText.addTextChangedListener(object : TextWatcher {
- override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
+ override fun beforeTextChanged(charSequence: CharSequence, start: Int, count: Int, after: Int) {
+ // Do nothing.
+ }
- override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
+ override fun onTextChanged(charSequence: CharSequence, start: Int, before: Int, count: Int) {
+ // Do nothing.
+ }
- override fun afterTextChanged(s: Editable) {
+ override fun afterTextChanged(editable: Editable) {
// Search for the text in the WebView if it is not null. Sometimes on resume after a period of non-use the WebView will be null.
- currentWebView?.findAllAsync(findOnPageEditText.text.toString())
+ currentWebView?.findAllAsync(editable.toString())
}
})
drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer))
drawerLayout.setDrawerTitle(GravityCompat.END, getString(R.string.bookmarks))
- // Load the bookmarks folder.
- loadBookmarksFolder()
-
// Handle clicks on bookmarks.
bookmarksListView.onItemClickListener = AdapterView.OnItemClickListener { _: AdapterView<*>?, _: View?, _: Int, id: Long ->
// Convert the id from long to int to match the format of the bookmarks database.
drawerLayout.addDrawerListener(object : DrawerLayout.DrawerListener {
override fun onDrawerSlide(drawerView: View, slideOffset: Float) {}
- override fun onDrawerOpened(drawerView: View) {}
+ override fun onDrawerOpened(drawerView: View) {
+ // Close the navigation drawer if requested. <https://redmine.stoutner.com/issues/1267>
+ if (closeNavigationDrawer) {
+ // Reset the close navigation drawer flag.
+ closeNavigationDrawer = false
+
+ // Close the navigation drawer.
+ drawerLayout.closeDrawer(GravityCompat.START)
+ }
+ }
override fun onDrawerClosed(drawerView: View) {}
override fun onDrawerStateChanged(newState: Int) {
if (newState == DrawerLayout.STATE_SETTLING || newState == DrawerLayout.STATE_DRAGGING) { // A drawer is opening or closing.
+ // Adjust the scroll position of the navigation drawer.
+ if (bottomAppBar && navigationDrawerFirstView) { // The bottom app bar is in use.
+ // Reset the navigation drawer first view flag.
+ navigationDrawerFirstView = false
+
+ // Get a handle for the navigation recycler view.
+ val navigationRecyclerView = navigationView.getChildAt(0) as RecyclerView
+
+ // Get the navigation linear layout manager.
+ val navigationLinearLayoutManager = navigationRecyclerView.layoutManager as LinearLayoutManager
+
+ // Scroll the navigation drawer to the bottom.
+ navigationLinearLayoutManager.scrollToPositionWithOffset(14, 0)
+ } else if (Build.VERSION.SDK_INT < 35 && navigationDrawerFirstView) { // The top app bar is in use and the API < 35 (which causes the drawer to scroll down for some reason).
+ // Reset the navigation drawer first view flag.
+ navigationDrawerFirstView = false
+
+ // Get a handle for the navigation recycler view.
+ val navigationRecyclerView = navigationView.getChildAt(0) as RecyclerView
+
+ // Get the navigation linear layout manager.
+ val navigationLinearLayoutManager = navigationRecyclerView.layoutManager as LinearLayoutManager
+
+ // Scroll the navigation drawer to the top. <
+ navigationLinearLayoutManager.scrollToPositionWithOffset(0, 0)
+ }
+
// Update the navigation menu items if the WebView is not null.
if (currentWebView != null) {
// Set the enabled status of the menu items.
navigationScrollToBottomMenuItem.title = getString(R.string.scroll_to_bottom)
// Set the icon.
- navigationScrollToBottomMenuItem.icon = AppCompatResources.getDrawable(applicationContext, R.drawable.move_down_enabled)
+ navigationScrollToBottomMenuItem.icon = AppCompatResources.getDrawable(applicationContext, R.drawable.move_to_bottom_enabled)
} else { // The WebView is not scrolled to the top.
// Set the title.
navigationScrollToBottomMenuItem.title = getString(R.string.scroll_to_top)
// Set the icon.
- navigationScrollToBottomMenuItem.icon = AppCompatResources.getDrawable(applicationContext, R.drawable.move_up_enabled)
+ navigationScrollToBottomMenuItem.icon = AppCompatResources.getDrawable(applicationContext, R.drawable.move_to_top_enabled)
}
// Display the number of blocked requests.
// Clear the focus from from the WebView if it is not null, which can happen if a user opens a drawer while the browser is being resumed.
// Clearing the focus from the WebView removes any text selection markers and context menus, which otherwise draw above the open drawers.
currentWebView?.clearFocus()
-
- if (bottomAppBar && navigationDrawerFirstView) {
- // Reset the navigation drawer first view flag.
- navigationDrawerFirstView = false
-
- // Get a handle for the navigation recycler view.
- val navigationRecyclerView = navigationView.getChildAt(0) as RecyclerView
-
- // Get the navigation linear layout manager.
- val navigationLinearLayoutManager = navigationRecyclerView.layoutManager as LinearLayoutManager
-
- // Scroll the navigation drawer to the bottom.
- navigationLinearLayoutManager.scrollToPositionWithOffset(13, 0)
- }
}
}
})
@SuppressLint("ClickableViewAccessibility")
override fun initializeWebView(nestedScrollWebView: NestedScrollWebView, pagePosition: Int, progressBar: ProgressBar, urlString: String, restoringState: Boolean) {
+ // Fix the bookmarks drawer top padding on API <= 29.
+ if (Build.VERSION.SDK_INT <= 29) {
+ // Set the top padding according to the app bar location.
+ if (bottomAppBar)
+ bookmarksListView.setPadding(bookmarksListView.paddingLeft, swipeRefreshLayout.top, bookmarksListView.paddingRight, bookmarksListView.paddingBottom)
+ else
+ bookmarksHeaderLinearLayout.setPadding(bookmarksHeaderLinearLayout.paddingLeft, appBarLayout.top, bookmarksHeaderLinearLayout.paddingRight, bookmarksHeaderLinearLayout.paddingBottom)
+ }
+
// Get the WebView theme.
val webViewTheme = sharedPreferences.getString(getString(R.string.webview_theme_key), getString(R.string.webview_theme_default_value))
}
}
- // Get a handle for the input method manager.
- val inputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
+ // Disable using the web cache.
+ nestedScrollWebView.settings.cacheMode = WebSettings.LOAD_NO_CACHE
// Set the app bar scrolling.
nestedScrollWebView.isNestedScrollingEnabled = scrollAppBar
}
}
- /* Hide the system bars.
- * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
- * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
- * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
- * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
- */
+ // Hide the system bars.
+ if (Build.VERSION.SDK_INT >= 30) {
+ // Set the system bars to display transiently when swiped.
+ windowInsetsController.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+
+ // Hide the system bars.
+ windowInsetsController.hide(WindowInsets.Type.systemBars())
+ } else {
+ /* Hide the system bars.
+ * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+ * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+ * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+ * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+ */
+
+ @Suppress("DEPRECATION")
+ window.addFlags(View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
+ }
+
+ // Disable fits system windows according to the configuration.
+ if (displayUnderCutouts) { // Display under cutouts.
+ // Disable fits system windows according to the API.
+ if (Build.VERSION.SDK_INT >= 35) { // The device is running API >= 35.
+ // Disable fits system windows.
+ browserFrameLayout.fitsSystemWindows = false
+
+ // Manually update the padding, which isn't done on API >= 35
+ browserFrameLayout.setPadding(0, 0, 0, 0)
+ } else { // The device is running API < 35.
+ // Disable fits system windows.
+ coordinatorLayout.fitsSystemWindows = false
+ bookmarksFrameLayout.fitsSystemWindows = false
+
+ // Remove any padding on the bookmarks frame layout.
+ bookmarksFrameLayout.setPadding(0, 0, 0, 0)
+ }
+ } else if (Build.VERSION.SDK_INT < 35) { // The device is running API < 35 and display under cutouts is disabled.
+ // Disable fits system windows to display under the navigation bar.
+ coordinatorLayout.fitsSystemWindows = false
+ bookmarksFrameLayout.fitsSystemWindows = false
- // The deprecated command can be switched to `WindowInsetsController` once the minimum API >= 30.
- @Suppress("DEPRECATION")
- rootFrameLayout.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+ // Remove any padding on the bookmarks frame layout.
+ bookmarksFrameLayout.setPadding(0, 0, 0, 0)
+ }
} else { // Switch to normal viewing mode.
// Show the app bar if it was hidden.
if (hideAppBar) {
}
}
- // Remove the `SYSTEM_UI` flags from the root frame layout. The deprecated command can be switched to `WindowInsetsController` once the minimum API >= 30.
- @Suppress("DEPRECATION")
- rootFrameLayout.systemUiVisibility = 0
+ // Enable fits system windows if display under cutouts is enabled.
+ if (displayUnderCutouts) {
+ // Enable fits system windows according to the API.
+ if (Build.VERSION.SDK_INT >= 35) {
+ browserFrameLayout.fitsSystemWindows = true
+ } else {
+ coordinatorLayout.fitsSystemWindows = true
+ bookmarksFrameLayout.fitsSystemWindows = true
+ }
+ } else if (Build.VERSION.SDK_INT < 35) { // The device is running API < 35 and display under cutouts is disabled.
+ // Enable fits system windows.
+ coordinatorLayout.fitsSystemWindows = true
+ bookmarksFrameLayout.fitsSystemWindows = true
+ }
+
+ // Show the system bars according to the API.
+ if (Build.VERSION.SDK_INT >= 30) {
+ // Show the system bars.
+ windowInsetsController.show(WindowInsets.Type.systemBars())
+ } else {
+ /* Show the system bars.
+ * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+ * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+ * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+ * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+ */
+
+ @Suppress("DEPRECATION")
+ window.clearFlags(View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
+ }
}
// Consume the double-tap.
swipeRefreshLayout.isEnabled = nestedScrollWebView.scrollY == 0
else // Disable swipe to refresh.
swipeRefreshLayout.isEnabled = false
-
- // Reinforce the system UI visibility flags if in full screen browsing mode.
- // This hides the status and navigation bars, which are displayed if other elements are shown, like dialog boxes, the options menu, or the keyboard.
- if (inFullScreenBrowsingMode) {
- /* Hide the system bars.
- * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
- * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
- * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
- * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
- */
-
- // The deprecated command can be switched to `WindowInsetsController` once the minimum API >= 30.
- @Suppress("DEPRECATION")
- rootFrameLayout.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
- }
}
// Set the web chrome client.
// Only update the favorite icon if the website has finished loading and the new favorite icon height is greater than the current favorite icon height.
// This prevents low resolution icons from replacing high resolution one.
// The check for the visibility of the progress bar can possibly be removed once https://redmine.stoutner.com/issues/747 is fixed.
- if ((progressBar.visibility == View.GONE) && (icon.height > nestedScrollWebView.getFavoriteIconHeight())) {
+ if ((progressBar.isGone) && (icon.height > nestedScrollWebView.getFavoriteIconHeight())) {
// Store the new favorite icon.
nestedScrollWebView.setFavoriteIcon(icon)
val tabFavoriteIconImageView = tabView.findViewById<ImageView>(R.id.favorite_icon_imageview)
// Display the favorite icon in the tab.
- tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true))
+ tabFavoriteIconImageView.setImageBitmap(icon.scale(128, 128))
}
}
}
// Hide the keyboard.
inputMethodManager.hideSoftInputFromWindow(nestedScrollWebView.windowToken, 0)
- // Hide the coordinator layout.
- coordinatorLayout.visibility = View.GONE
+ // Hide the browser view according to the API.
+ if ((Build.VERSION.SDK_INT < 35) && displayUnderCutouts) { // The device is running API < 35 and display under cutouts is enabled.
+ // Hide the tab linear layout.
+ tabsLinearLayout.visibility = View.GONE
- /* Hide the system bars.
- * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
- * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
- * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
- * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
- */
+ // Hide the app bar.
+ appBar.hide()
- // The deprecated command can be switched to `WindowInsetsController` once the minimum API >= 30.
- @Suppress("DEPRECATION")
- rootFrameLayout.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+ // Hide the swipe refresh layout (which includes the WebView).
+ swipeRefreshLayout.visibility = View.GONE
+
+ // Disable fits system windows.
+ coordinatorLayout.fitsSystemWindows = false
+ bookmarksFrameLayout.fitsSystemWindows = false
+
+ // Remove any padding from the bookmark frame layout
+ bookmarksFrameLayout.setPadding(0, 0, 0, 0)
+ } else { // The device is running API >= 35 or display under cutouts is disabled.
+ // Hide the browser view.
+ browserFrameLayout.visibility = View.GONE
+ }
+
+ // Hide the system bars according to the API.
+ if (Build.VERSION.SDK_INT >= 30) {
+ // Set the system bars to show transiently when swiped.
+ windowInsetsController.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+
+ // Hide the system bars.
+ windowInsetsController.hide(WindowInsets.Type.systemBars())
+ } else {
+ /* Hide the system bars.
+ * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+ * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+ * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+ * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+ */
+
+ @Suppress("DEPRECATION")
+ window.addFlags(View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
+ }
// Disable the sliding drawers.
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
- // Add the video view to the full screen video frame layout.
- fullScreenVideoFrameLayout.addView(video)
+ if ((Build.VERSION.SDK_INT < 35) && displayUnderCutouts) {
+ // Add the video view to the full screen video frame layout.
+ oldFullScreenVideoFrameLayout.addView(video)
+
+ // Show the full screen video frame layout.
+ oldFullScreenVideoFrameLayout.visibility = View.VISIBLE
+
+ // Disable the screen timeout while the video is playing. YouTube does this automatically, but not all other videos do.
+ oldFullScreenVideoFrameLayout.keepScreenOn = true
+ } else {
+ // Add the video view to the full screen video frame layout.
+ fullScreenVideoFrameLayout.addView(video)
- // Show the full screen video frame layout.
- fullScreenVideoFrameLayout.visibility = View.VISIBLE
+ // Show the full screen video frame layout.
+ fullScreenVideoFrameLayout.visibility = View.VISIBLE
- // Disable the screen timeout while the video is playing. YouTube does this automatically, but not all other videos do.
- fullScreenVideoFrameLayout.keepScreenOn = true
+ // Disable the screen timeout while the video is playing. YouTube does this automatically, but not all other videos do.
+ fullScreenVideoFrameLayout.keepScreenOn = true
+ }
}
// Exit full screen video.
// Store the file path callback.
fileChooserCallback = filePathCallback
- // Create an intent to open a chooser based on the file chooser parameters.
- val fileChooserIntent = fileChooserParams.createIntent()
-
- // Check to see if the file chooser intent resolves to an installed package.
- if (fileChooserIntent.resolveActivity(packageManager) != null) { // The file chooser intent is fine.
- // Launch the file chooser intent.
- browseFileUploadActivityResultLauncher.launch(fileChooserIntent)
- } else { // The file chooser intent will cause a crash.
- // Create a generic intent to open a chooser.
- val genericFileChooserIntent = Intent(Intent.ACTION_GET_CONTENT)
+ // Create a file chooser intent.
+ val fileChooserIntent = Intent(Intent.ACTION_GET_CONTENT)
- // Request an openable file.
- genericFileChooserIntent.addCategory(Intent.CATEGORY_OPENABLE)
+ // Request an openable file.
+ fileChooserIntent.addCategory(Intent.CATEGORY_OPENABLE)
- // Set the file type to everything.
- genericFileChooserIntent.type = "*/*"
+ // Set the file type to everything. The file chooser params cannot be used to create the intent because it only selects the first specified file type.
+ // See <https://redmine.stoutner.com/issues/1197>.
+ fileChooserIntent.type = "*/*"
- // Launch the generic file chooser intent.
- browseFileUploadActivityResultLauncher.launch(genericFileChooserIntent)
- }
+ // Launch the file chooser intent.
+ browseFileUploadActivityResultLauncher.launch(fileChooserIntent)
// Handle the event.
return true
val emailIntent = Intent(Intent.ACTION_SENDTO)
// Parse the url and set it as the data for the intent.
- emailIntent.data = Uri.parse(requestUrlString)
+ emailIntent.data = requestUrlString.toUri()
// Open the email program in a new task instead of as part of Privacy Browser.
emailIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
val dialIntent = Intent(Intent.ACTION_DIAL)
// Add the phone number to the intent.
- dialIntent.data = Uri.parse(requestUrlString)
+ dialIntent.data = requestUrlString.toUri()
// Open the dialer in a new task instead of as part of Privacy Browser.
dialIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
val genericIntent = Intent(Intent.ACTION_VIEW)
// Add the URL to the intent.
- genericIntent.data = Uri.parse(requestUrlString)
+ genericIntent.data = requestUrlString.toUri()
// List all apps that can handle the URL instead of just opening the first one.
genericIntent.addCategory(Intent.CATEGORY_BROWSABLE)
// Instantiate an HTTP authentication dialog.
val httpAuthenticationDialogFragment = HttpAuthenticationDialog.displayDialog(host, realm, nestedScrollWebView.webViewFragmentId)
- // Show the HTTP authentication dialog.
- httpAuthenticationDialogFragment.show(supportFragmentManager, getString(R.string.http_authentication))
+ // Try to show the dialog. WebView can receive an HTTP authentication request even after the app has been paused. Attempting to display a dialog in that state leads to a crash.
+ try {
+ // Show the HTTP authentication dialog.
+ httpAuthenticationDialogFragment.show(supportFragmentManager, getString(R.string.http_authentication))
+ } catch (exception: Exception) { // The dialog could not be shown.
+ // Add the dialog to the pending dialog array list. It will be displayed in `onStart()`.
+ pendingDialogsArrayList.add(PendingDialogDataClass(httpAuthenticationDialogFragment, getString(R.string.http_authentication)))
+ }
}
override fun onPageStarted(webView: WebView, url: String, favicon: Bitmap?) {
nestedScrollWebView.currentIpAddresses = ""
// Get a URI for the current URL.
- val currentUri = Uri.parse(url)
+ val currentUri = url.toUri()
// Get the current domain name.
val currentDomainName = currentUri.host
optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled)
}
+ // Clear the cache. `true` includes disk files.
+ nestedScrollWebView.clearCache(true)
+
// Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
// which links to `/data/data/com.stoutner.privacybrowser.standard`.
val privateDataDirectoryString = applicationInfo.dataDir
- // Clear the cache, history, and logcat if Incognito Mode is enabled.
+ // Clear the history, and logcat if Incognito Mode is enabled.
if (incognitoModeEnabled) {
- // Clear the cache. `true` includes disk files.
- nestedScrollWebView.clearCache(true)
-
// Clear the back/forward history.
nestedScrollWebView.clearHistory()
// Check to see if the URL is `about:blank`.
if (currentUrl == "about:blank") { // The WebView is blank.
// Display the hint in the URL edit text.
- urlEditText.setText("")
+ urlEditText.text = null
// Request focus for the URL text box.
urlEditText.requestFocus()
// Instantiate an SSL certificate error alert dialog.
val sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error, nestedScrollWebView.webViewFragmentId)
- // Try to show the dialog. The SSL error handler continues to function even when the WebView is paused. Attempting to display a dialog in that state leads to a crash.
+ // Try to show the dialog. The SSL error handler continues to function even when the app has been stopped. Attempting to display a dialog in that state leads to a crash.
try {
// Show the SSL certificate error dialog.
sslCertificateErrorDialogFragment.show(supportFragmentManager, getString(R.string.ssl_certificate_error))
private fun loadBookmarksFolder() {
// Update the bookmarks cursor with the contents of the bookmarks database for the current folder.
- bookmarksCursor = bookmarksDatabaseHelper!!.getBookmarksByDisplayOrder(currentBookmarksFolderId)
+ bookmarksCursor = if (sortBookmarksAlphabetically)
+ bookmarksDatabaseHelper!!.getBookmarksSortedAlphabetically(currentBookmarksFolderId)
+ else
+ bookmarksDatabaseHelper!!.getBookmarksByDisplayOrder(currentBookmarksFolderId)
// Populate the bookmarks cursor adapter.
bookmarksCursorAdapter = object : CursorAdapter(this, bookmarksCursor, false) {
private fun loadUrlFromTextBox() {
// Get the text from URL text box and convert it to a string. trim() removes white spaces from the beginning and end of the string.
- var unformattedUrlString = urlEditText.text.toString().trim { it <= ' ' }
+ var unformattedUrlString = urlEditText.text.toString().trim()
// Create the formatted URL string.
var urlString = ""
// Get the favorite icon image view from the tab.
val tabFavoriteIconImageView = tabView.findViewById<ImageView>(R.id.favorite_icon_imageview)
- // Set the previous favorite icon.
+ // Store the previous favorite icon.
if (previousFavoriteIcon == null)
- tabFavoriteIconImageView.setImageBitmap(defaultFavoriteIconBitmap)
+ currentWebView!!.setFavoriteIcon(defaultFavoriteIconBitmap)
else
- tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(previousFavoriteIcon, 64, 64, true))
+ currentWebView!!.setFavoriteIcon(previousFavoriteIcon)
+
+ // Display the previous favorite icon in the tab.
+ tabFavoriteIconImageView.setImageBitmap(currentWebView!!.getFavoriteIcon().scale(128, 128))
// Load the history entry.
currentWebView!!.goBackOrForward(steps)
if (mhtCheckBox.isChecked) { // Force opening of an MHT file.
try {
// Get the MHT file input stream.
- val mhtFileInputStream = contentResolver.openInputStream(Uri.parse(openFilePath))
+ val mhtFileInputStream = contentResolver.openInputStream(openFilePath.toUri())
// Create a temporary MHT file.
val temporaryMhtFile = File.createTempFile(TEMPORARY_MHT_FILE, ".mht", cacheDir)
currentWebView!!.loadUrl(openFilePath)
}
}
+
// The view parameter cannot be removed because it is called from the layout onClick.
fun openNavigationDrawer(@Suppress("UNUSED_PARAMETER")view: View) {
// Open the navigation drawer.
val openWithAppIntent = Intent(Intent.ACTION_VIEW)
// Set the URI but not the MIME type. This should open all available apps.
- openWithAppIntent.data = Uri.parse(url)
+ openWithAppIntent.data = url.toUri()
// Flag the intent to open in a new task.
openWithAppIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
val openWithBrowserIntent = Intent(Intent.ACTION_VIEW)
// Set the URI and the MIME type. `"text/html"` should load browser options.
- openWithBrowserIntent.setDataAndType(Uri.parse(url), "text/html")
+ openWithBrowserIntent.setDataAndType(url.toUri(), "text/html")
// Flag the intent to open in a new task.
openWithBrowserIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
val downloadManager = getSystemService(DOWNLOAD_SERVICE) as DownloadManager
// Parse the URL.
- val downloadRequest = DownloadManager.Request(Uri.parse(saveUrlString))
+ val downloadRequest = DownloadManager.Request(saveUrlString.toUri())
// Pass cookies to download manager if cookies are enabled. This is required to download files from websites that require a login.
// Code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
val downloadIntent = Intent()
// Set the URI and the mime type.
- downloadIntent.setDataAndType(Uri.parse(url), "text/html")
+ downloadIntent.setDataAndType(url.toUri(), "text/html")
// Flag the intent to open in a new task.
downloadIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
// Stop the swipe to refresh indicator if it is running
swipeRefreshLayout.isRefreshing = false
+ // Open the navigation drawer if the bottom app bar is enabled and this is the first tab. <https://redmine.stoutner.com/issues/1267>
+ if (displayingInitialTab && bottomAppBar) {
+ // Open the navigation drawer.
+ drawerLayout.openDrawer(GravityCompat.START)
+
+ // Set the close navigation drawer flag.
+ closeNavigationDrawer = true
+ }
+
+ // Set the displaying initial tab flag to be false.
+ displayingInitialTab = false
+
// Try to set the current WebView. This will fail if the WebView has not yet been populated.
try {
// Get the WebView tab fragment.
// Update the privacy icons. `true` redraws the icons in the app bar.
updatePrivacyIcons(true)
- // Get a handle for the input method manager.
- val inputMethodManager = (getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager)
-
// Get the current URL.
- val urlString = currentWebView!!.url
+ val urlString = currentWebView!!.currentUrl
// Update the URL edit text if not loading a new intent. Otherwise, this will be handled by `onPageStarted()` (if called) and `onPageFinished()`.
if (!loadingNewIntent) { // A new intent is not being loaded.
- if ((urlString == null) || (urlString == "about:blank")) { // The WebView is blank.
+ if (urlString.isBlank()) { // The WebView is blank.
// Display the hint in the URL edit text.
- urlEditText.setText("")
+ urlEditText.text = null
// Request focus for the URL text box.
urlEditText.requestFocus()