/*
- * Copyright © 2019-2021 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2019-2023 Soren Stoutner <soren@stoutner.com>.
*
- * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
*
- * Privacy Browser is free software: you can redistribute it and/or modify
+ * 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 is distributed in the hope that it will be useful,
+ * 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. If not, see <http://www.gnu.org/licenses/>.
+ * along with Privacy Browser Android. If not, see <http://www.gnu.org/licenses/>.
*/
package com.stoutner.privacybrowser.views
+import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
import android.os.Bundle
import android.util.AttributeSet
import android.view.MotionEvent
-import android.webkit.WebView
-import android.webkit.SslErrorHandler
import android.webkit.HttpAuthHandler
+import android.webkit.SslErrorHandler
+import android.webkit.WebView
-import androidx.core.content.ContextCompat
+import androidx.appcompat.content.res.AppCompatResources.getDrawable
import androidx.core.view.NestedScrollingChild2
import androidx.core.view.NestedScrollingChildHelper
import androidx.core.view.ViewCompat
import com.stoutner.privacybrowser.R
+import com.stoutner.privacybrowser.activities.MainWebViewActivity
import java.util.Collections
import java.util.Date
import kotlin.collections.ArrayList
-import kotlin.jvm.JvmOverloads
-
-// Define the saved state constants.
-private const val DOMAIN_SETTINGS_APPLIED = "domain_settings_applied"
-private const val DOMAIN_SETTINGS_DATABASE_ID = "domain_settings_database_id"
-private const val CURRENT_DOMAIN_NAME = "current_domain_name"
-private const val CURRENT_URl = "current_url"
-private const val ACCEPT_COOKIES = "accept_cookies"
-private const val EASYLIST_ENABLED = "easylist_enabled"
-private const val EASYPRIVACY_ENABLED = "easyprivacy_enabled"
-private const val FANBOYS_ANNOYANCE_LIST_ENABLED = "fanboys_annoyance_list_enabled"
-private const val FANBOYS_SOCIAL_BLOCKING_LIST_ENABLED = "fanboys_social_blocking_list_enabled"
-private const val ULTRALIST_ENABLED = "ultralist_enabled"
-private const val ULTRAPRIVACY_ENABLED = "ultraprivacy_enabled"
-private const val BLOCK_ALL_THIRD_PARTY_REQUESTS = "block_all_third_party_requests"
-private const val HAS_PINNED_SSL_CERTIFICATE = "has_pinned_ssl_certificate"
-private const val PINNED_SSL_ISSUED_TO_CNAME = "pinned_ssl_issued_to_cname"
-private const val PINNED_SSL_ISSUED_TO_ONAME = "pinned_ssl_issued_to_oname"
-private const val PINNED_SSL_ISSUED_TO_UNAME = "pinned_ssl_issued_to_uname"
-private const val PINNED_SSL_ISSUED_BY_CNAME = "pinned_ssl_issued_by_cname"
-private const val PINNED_SSL_ISSUED_BY_ONAME = "pinned_ssl_issued_by_oname"
-private const val PINNED_SSL_ISSUED_BY_UNAME = "pinned_ssl_issued_by_uname"
-private const val PINNED_SSL_START_DATE = "pinned_ssl_start_date"
-private const val PINNED_SSL_END_DATE = "pinned_ssl_end_date"
-private const val PINNED_IP_ADDRESSES = "pinned_ip_addresses"
-private const val IGNORE_PINNED_DOMAIN_INFORMATION = "ignore_pinned_domain_information"
-private const val SWIPE_TO_REFRESH = "swipe_to_refresh"
-private const val JAVASCRIPT_ENABLED = "javascript_enabled"
-private const val DOM_STORAGE_ENABLED = "dom_storage_enabled"
-private const val USER_AGENT = "user_agent"
-private const val WIDE_VIEWPORT = "wide_viewport"
-private const val FONT_SIZE = "font_size"
+// Define the public constants.
+const val BLOCKED_REQUESTS = 0
+const val EASYLIST = 1
+const val EASYPRIVACY = 2
+const val FANBOYS_ANNOYANCE_LIST = 3
+const val FANBOYS_SOCIAL_BLOCKING_LIST = 4
+const val ULTRALIST = 5
+const val ULTRAPRIVACY = 6
+const val THIRD_PARTY_REQUESTS = 7
+
+// Define the private class constants.
+private const val ACCEPT_COOKIES = "A"
+private const val BLOCK_ALL_THIRD_PARTY_REQUESTS = "B"
+private const val CURRENT_DOMAIN_NAME = "C"
+private const val CURRENT_URL = "D"
+private const val DOM_STORAGE_ENABLED = "E"
+private const val DOMAIN_SETTINGS_APPLIED = "F"
+private const val DOMAIN_SETTINGS_DATABASE_ID = "G"
+private const val EASYLIST_ENABLED = "H"
+private const val EASYPRIVACY_ENABLED = "I"
+private const val FANBOYS_ANNOYANCE_LIST_ENABLED = "J"
+private const val FANBOYS_SOCIAL_BLOCKING_LIST_ENABLED = "K"
+private const val FONT_SIZE = "L"
+private const val HAS_PINNED_SSL_CERTIFICATE = "M"
+private const val IGNORE_PINNED_DOMAIN_INFORMATION = "N"
+private const val JAVASCRIPT_ENABLED = "O"
+private const val PINNED_IP_ADDRESSES = "P"
+private const val PINNED_SSL_END_DATE = "Q"
+private const val PINNED_SSL_ISSUED_BY_CNAME = "R"
+private const val PINNED_SSL_ISSUED_BY_ONAME = "S"
+private const val PINNED_SSL_ISSUED_BY_UNAME = "T"
+private const val PINNED_SSL_ISSUED_TO_CNAME = "U"
+private const val PINNED_SSL_ISSUED_TO_ONAME = "V"
+private const val PINNED_SSL_ISSUED_TO_UNAME = "W"
+private const val PINNED_SSL_START_DATE = "X"
+private const val SWIPE_TO_REFRESH = "Y"
+private const val ULTRALIST_ENABLED = "Z"
+private const val ULTRAPRIVACY_ENABLED = "AA"
+private const val USER_AGENT = "AB"
+private const val WIDE_VIEWPORT = "AC"
// NestedScrollWebView extends WebView to handle nested scrolls (scrolling the app bar off the screen). It also stores extra information about the state of the WebView used by Privacy Browser.
class NestedScrollWebView @JvmOverloads constructor(context: Context, attributeSet: AttributeSet? = null, defaultStyle: Int = android.R.attr.webViewStyle) : WebView(context, attributeSet, defaultStyle),
NestedScrollingChild2 {
- companion object {
- // Define the companion object blocklists constants. These can be moved to class constants once all of the code has transitioned to Kotlin.
- const val BLOCKED_REQUESTS = 0
- const val EASYLIST = 1
- const val EASYPRIVACY = 2
- const val FANBOYS_ANNOYANCE_LIST = 3
- const val FANBOYS_SOCIAL_BLOCKING_LIST = 4
- const val ULTRALIST = 5
- const val ULTRAPRIVACY = 6
- const val THIRD_PARTY_REQUESTS = 7
- }
-
// Define the public variables.
var acceptCookies = false
var blockAllThirdPartyRequests = false
var httpAuthHandler: HttpAuthHandler? = null
var ignorePinnedDomainInformation = false
var pinnedIpAddresses = ""
+ var previousFavoriteIconDrawable: Drawable? = null
+ var previousWebpageTitle = ""
var sslErrorHandler: SslErrorHandler? = null
var swipeToRefresh = false
var ultraListEnabled = true
var waitingForProxyUrlString = ""
var webViewFragmentId: Long = 0
-
// Define the private variables.
private val nestedScrollingChildHelper: NestedScrollingChildHelper = NestedScrollingChildHelper(this)
- private lateinit var favoriteOrDefaultIcon: Bitmap
+ private lateinit var favoriteIcon: Bitmap
+ private var favoriteIconHeight = 0
private var previousYPosition = 0 // The previous Y position needs to be tracked between motion events.
private var hasPinnedSslCertificate = false
private var pinnedSslIssuedToCName = ""
initializeFavoriteIcon()
}
-
// Favorite or default icon.
fun initializeFavoriteIcon() {
- // Get the default favorite icon drawable. `ContextCompat` must be used until API >= 21.
- val favoriteIconDrawable = ContextCompat.getDrawable(context, R.drawable.world)
+ // Get the default favorite icon drawable.
+ val favoriteIconDrawable = getDrawable(context, R.drawable.world)
// Cast the favorite icon drawable to a bitmap drawable.
val favoriteIconBitmapDrawable = (favoriteIconDrawable as BitmapDrawable?)!!
// Store the default icon bitmap.
- favoriteOrDefaultIcon = favoriteIconBitmapDrawable.bitmap
+ favoriteIcon = favoriteIconBitmapDrawable.bitmap
+
+ // Set the favorite icon height to be 0. This way any favorite icons presented by the website will overwrite it.
+ favoriteIconHeight = 0
}
- fun setFavoriteOrDefaultIcon(icon: Bitmap) {
+ fun setFavoriteIcon(icon: Bitmap) {
+ // Store the current favorite icon height.
+ favoriteIconHeight = icon.height
+
// Scale the favorite icon bitmap down if it is larger than 256 x 256. Filtering uses bilinear interpolation.
- favoriteOrDefaultIcon = if (icon.height > 256 || icon.width > 256) {
+ favoriteIcon = if (icon.height > 256 || icon.width > 256) {
Bitmap.createScaledBitmap(icon, 256, 256, true)
} else {
// Store the icon as presented.
}
}
- fun getFavoriteOrDefaultIcon(): Bitmap {
- // Return the favorite or default icon. This is the only way to return a non-nullable variable while retaining the custom initialization and setter functions above.
- return favoriteOrDefaultIcon
+ fun getFavoriteIcon(): Bitmap {
+ // Return the favorite icon. This is the only way to return a non-nullable variable while retaining the custom initialization and setter functions above.
+ return favoriteIcon
}
+ fun getFavoriteIconHeight(): Int {
+ // Return the favorite icon height.
+ return favoriteIconHeight
+ }
// Reset the handlers.
fun resetSslErrorHandler() {
hasPinnedSslCertificate = true
}
- fun getPinnedSslCertificate(): ArrayList<Any> {
- // Initialize an array list.
- val arrayList = ArrayList<Any>()
-
+ fun getPinnedSslCertificate(): Pair<Array<String>, Array<Date>> {
// Create the SSL certificate string array.
val sslCertificateStringArray = arrayOf(pinnedSslIssuedToCName, pinnedSslIssuedToOName, pinnedSslIssuedToUName, pinnedSslIssuedByCName, pinnedSslIssuedByOName, pinnedSslIssuedByUName)
// Create the SSL certificate date array.
val sslCertificateDateArray = arrayOf(pinnedSslStartDate, pinnedSslEndDate)
- // Add the arrays to the array list.
- arrayList.add(sslCertificateStringArray)
- arrayList.add(sslCertificateDateArray)
-
- // Return the pinned SSL certificate array list.
- return arrayList
+ // Return the pinned SSL certificate pair.
+ return Pair(sslCertificateStringArray, sslCertificateDateArray)
}
fun clearPinnedSslCertificate() {
// Resource request counters.
- fun incrementRequestsCount(blocklist: Int) {
- // Increment the count of the indicated blocklist.
- when (blocklist) {
+ fun incrementRequestsCount(filterList: Int) {
+ // Increment the count of the indicated filter list.
+ when (filterList) {
BLOCKED_REQUESTS -> blockedRequests++
EASYLIST -> easyListBlockedRequests++
EASYPRIVACY -> easyPrivacyBlockedRequests++
}
}
- fun getRequestsCount(blocklist: Int): Int {
- // Return the count of the indicated blocklist.
- return when (blocklist) {
+ fun getRequestsCount(filterList: Int): Int {
+ // Return the count of the indicated filter list.
+ return when (filterList) {
BLOCKED_REQUESTS -> blockedRequests
EASYLIST -> easyListBlockedRequests
EASYPRIVACY -> easyPrivacyBlockedRequests
return computeVerticalScrollRange()
}
+ override fun onOverScrolled(scrollX: Int, scrollY: Int, clampedX: Boolean, clampedY: Boolean) {
+ // Run the default commands.
+ super.onOverScrolled(scrollX, scrollY, clampedX, clampedY)
+
+ // Display the bottom app bar if it has been hidden and the WebView was over-scrolled at the top of the screen.
+ if ((MainWebViewActivity.appBarLayout.translationY != 0f) && (scrollY == 0) && clampedY) {
+ // Animate the bottom app bar onto the screen.
+ val objectAnimator = ObjectAnimator.ofFloat(MainWebViewActivity.appBarLayout, "translationY", 0f)
+
+ // Make it so.
+ objectAnimator.start()
+ }
+ }
// Handle touches.
@SuppressLint("ClickableViewAccessibility")
}
- // Save and restore state.
+ // Save the state.
fun saveNestedScrollWebViewState(): Bundle {
// Create a saved state bundle.
val savedState = Bundle()
// Populate the saved state bundle.
+ savedState.putBoolean(ACCEPT_COOKIES, acceptCookies)
+ savedState.putBoolean(BLOCK_ALL_THIRD_PARTY_REQUESTS, blockAllThirdPartyRequests)
+ savedState.putString(CURRENT_DOMAIN_NAME, currentDomainName)
+ savedState.putString(CURRENT_URL, currentUrl)
+ savedState.putBoolean(DOM_STORAGE_ENABLED, this.settings.domStorageEnabled)
savedState.putBoolean(DOMAIN_SETTINGS_APPLIED, domainSettingsApplied)
savedState.putInt(DOMAIN_SETTINGS_DATABASE_ID, domainSettingsDatabaseId)
- savedState.putString(CURRENT_DOMAIN_NAME, currentDomainName)
- savedState.putString(CURRENT_URl, currentUrl)
- savedState.putBoolean(ACCEPT_COOKIES, acceptCookies)
savedState.putBoolean(EASYLIST_ENABLED, easyListEnabled)
savedState.putBoolean(EASYPRIVACY_ENABLED, easyPrivacyEnabled)
savedState.putBoolean(FANBOYS_ANNOYANCE_LIST_ENABLED, fanboysAnnoyanceListEnabled)
savedState.putBoolean(FANBOYS_SOCIAL_BLOCKING_LIST_ENABLED, fanboysSocialBlockingListEnabled)
- savedState.putBoolean(ULTRALIST_ENABLED, ultraListEnabled)
- savedState.putBoolean(ULTRAPRIVACY_ENABLED, ultraPrivacyEnabled)
- savedState.putBoolean(BLOCK_ALL_THIRD_PARTY_REQUESTS, blockAllThirdPartyRequests)
+ savedState.putInt(FONT_SIZE, this.settings.textZoom)
savedState.putBoolean(HAS_PINNED_SSL_CERTIFICATE, hasPinnedSslCertificate)
- savedState.putString(PINNED_SSL_ISSUED_TO_CNAME, pinnedSslIssuedToCName)
- savedState.putString(PINNED_SSL_ISSUED_TO_ONAME, pinnedSslIssuedToOName)
- savedState.putString(PINNED_SSL_ISSUED_TO_UNAME, pinnedSslIssuedToUName)
+ savedState.putBoolean(IGNORE_PINNED_DOMAIN_INFORMATION, ignorePinnedDomainInformation)
+ savedState.putBoolean(JAVASCRIPT_ENABLED, this.settings.javaScriptEnabled)
+ savedState.putString(PINNED_IP_ADDRESSES, pinnedIpAddresses)
+ savedState.putLong(PINNED_SSL_END_DATE, pinnedSslEndDate.time)
savedState.putString(PINNED_SSL_ISSUED_BY_CNAME, pinnedSslIssuedByCName)
savedState.putString(PINNED_SSL_ISSUED_BY_ONAME, pinnedSslIssuedByOName)
savedState.putString(PINNED_SSL_ISSUED_BY_UNAME, pinnedSslIssuedByUName)
+ savedState.putString(PINNED_SSL_ISSUED_TO_CNAME, pinnedSslIssuedToCName)
+ savedState.putString(PINNED_SSL_ISSUED_TO_ONAME, pinnedSslIssuedToOName)
+ savedState.putString(PINNED_SSL_ISSUED_TO_UNAME, pinnedSslIssuedToUName)
savedState.putLong(PINNED_SSL_START_DATE, pinnedSslStartDate.time)
- savedState.putLong(PINNED_SSL_END_DATE, pinnedSslEndDate.time)
- savedState.putString(PINNED_IP_ADDRESSES, pinnedIpAddresses)
- savedState.putBoolean(IGNORE_PINNED_DOMAIN_INFORMATION, ignorePinnedDomainInformation)
savedState.putBoolean(SWIPE_TO_REFRESH, swipeToRefresh)
- savedState.putBoolean(JAVASCRIPT_ENABLED, this.settings.javaScriptEnabled)
- savedState.putBoolean(DOM_STORAGE_ENABLED, this.settings.domStorageEnabled)
+ savedState.putBoolean(ULTRALIST_ENABLED, ultraListEnabled)
+ savedState.putBoolean(ULTRAPRIVACY_ENABLED, ultraPrivacyEnabled)
savedState.putString(USER_AGENT, this.settings.userAgentString)
savedState.putBoolean(WIDE_VIEWPORT, this.settings.useWideViewPort)
- savedState.putInt(FONT_SIZE, this.settings.textZoom)
// Return the saved state bundle.
return savedState
}
+ // Restore the state.
fun restoreNestedScrollWebViewState(savedState: Bundle) {
// Restore the class variables.
+ acceptCookies = savedState.getBoolean(ACCEPT_COOKIES)
+ blockAllThirdPartyRequests = savedState.getBoolean(BLOCK_ALL_THIRD_PARTY_REQUESTS)
+ currentDomainName = savedState.getString(CURRENT_DOMAIN_NAME)!!
+ currentUrl = savedState.getString(CURRENT_URL)!!
+ this.settings.domStorageEnabled = savedState.getBoolean(DOM_STORAGE_ENABLED)
domainSettingsApplied = savedState.getBoolean(DOMAIN_SETTINGS_APPLIED)
domainSettingsDatabaseId = savedState.getInt(DOMAIN_SETTINGS_DATABASE_ID)
- currentDomainName = savedState.getString(CURRENT_DOMAIN_NAME)!!
- currentUrl = savedState.getString(CURRENT_URl)!!
- acceptCookies = savedState.getBoolean(ACCEPT_COOKIES)
easyListEnabled = savedState.getBoolean(EASYLIST_ENABLED)
easyPrivacyEnabled = savedState.getBoolean(EASYPRIVACY_ENABLED)
fanboysAnnoyanceListEnabled = savedState.getBoolean(FANBOYS_ANNOYANCE_LIST_ENABLED)
fanboysSocialBlockingListEnabled = savedState.getBoolean(FANBOYS_SOCIAL_BLOCKING_LIST_ENABLED)
- ultraListEnabled = savedState.getBoolean(ULTRALIST_ENABLED)
- ultraPrivacyEnabled = savedState.getBoolean(ULTRAPRIVACY_ENABLED)
- blockAllThirdPartyRequests = savedState.getBoolean(BLOCK_ALL_THIRD_PARTY_REQUESTS)
+ this.settings.textZoom = savedState.getInt(FONT_SIZE)
hasPinnedSslCertificate = savedState.getBoolean(HAS_PINNED_SSL_CERTIFICATE)
- pinnedSslIssuedToCName = savedState.getString(PINNED_SSL_ISSUED_TO_CNAME)!!
- pinnedSslIssuedToOName = savedState.getString(PINNED_SSL_ISSUED_TO_ONAME)!!
- pinnedSslIssuedToUName = savedState.getString(PINNED_SSL_ISSUED_TO_UNAME)!!
+ ignorePinnedDomainInformation = savedState.getBoolean(IGNORE_PINNED_DOMAIN_INFORMATION)
+ this.settings.javaScriptEnabled = savedState.getBoolean(JAVASCRIPT_ENABLED)
+ pinnedIpAddresses = savedState.getString(PINNED_IP_ADDRESSES)!!
+ pinnedSslEndDate = Date(savedState.getLong(PINNED_SSL_END_DATE))
pinnedSslIssuedByCName = savedState.getString(PINNED_SSL_ISSUED_BY_CNAME)!!
pinnedSslIssuedByOName = savedState.getString(PINNED_SSL_ISSUED_BY_ONAME)!!
pinnedSslIssuedByUName = savedState.getString(PINNED_SSL_ISSUED_BY_UNAME)!!
+ pinnedSslIssuedToCName = savedState.getString(PINNED_SSL_ISSUED_TO_CNAME)!!
+ pinnedSslIssuedToOName = savedState.getString(PINNED_SSL_ISSUED_TO_ONAME)!!
+ pinnedSslIssuedToUName = savedState.getString(PINNED_SSL_ISSUED_TO_UNAME)!!
pinnedSslStartDate = Date(savedState.getLong(PINNED_SSL_START_DATE))
- pinnedSslEndDate = Date(savedState.getLong(PINNED_SSL_END_DATE))
- pinnedIpAddresses = savedState.getString(PINNED_IP_ADDRESSES)!!
- ignorePinnedDomainInformation = savedState.getBoolean(IGNORE_PINNED_DOMAIN_INFORMATION)
swipeToRefresh = savedState.getBoolean(SWIPE_TO_REFRESH)
- this.settings.javaScriptEnabled = savedState.getBoolean(JAVASCRIPT_ENABLED)
- this.settings.domStorageEnabled = savedState.getBoolean(DOM_STORAGE_ENABLED)
+ ultraListEnabled = savedState.getBoolean(ULTRALIST_ENABLED)
+ ultraPrivacyEnabled = savedState.getBoolean(ULTRAPRIVACY_ENABLED)
this.settings.userAgentString = savedState.getString(USER_AGENT)
this.settings.useWideViewPort = savedState.getBoolean(WIDE_VIEWPORT)
- this.settings.textZoom = savedState.getInt(FONT_SIZE)
}
// Dispatch a nested fling with the specified velocity.
return nestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed)
}
-}
\ No newline at end of file
+}