--- /dev/null
+/*
+ * Copyright © 2019-2021 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ *
+ * Privacy Browser 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,
+ * 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/>.
+ */
+
+package com.stoutner.privacybrowser.views
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.drawable.BitmapDrawable
+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 androidx.core.content.ContextCompat
+import androidx.core.view.NestedScrollingChild2
+import androidx.core.view.NestedScrollingChildHelper
+import androidx.core.view.ViewCompat
+
+import com.stoutner.privacybrowser.R
+
+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"
+
+// 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 currentDomainName = ""
+ var currentIpAddresses = ""
+ var currentUrl = ""
+ var domainSettingsApplied = false
+ var domainSettingsDatabaseId = 0
+ var easyListEnabled = true
+ var easyPrivacyEnabled = true
+ var fanboysAnnoyanceListEnabled = true
+ var fanboysSocialBlockingListEnabled = true
+ var httpAuthHandler: HttpAuthHandler? = null
+ var ignorePinnedDomainInformation = false
+ var pinnedIpAddresses = ""
+ var sslErrorHandler: SslErrorHandler? = null
+ var swipeToRefresh = false
+ var ultraListEnabled = true
+ var ultraPrivacyEnabled = true
+ var waitingForProxyUrlString = ""
+ var webViewFragmentId: Long = 0
+
+
+ // Define the private variables.
+ private val nestedScrollingChildHelper: NestedScrollingChildHelper = NestedScrollingChildHelper(this)
+ private lateinit var favoriteOrDefaultIcon: Bitmap
+ private var previousYPosition = 0 // The previous Y position needs to be tracked between motion events.
+ private var hasPinnedSslCertificate = false
+ private var pinnedSslIssuedToCName = ""
+ private var pinnedSslIssuedToOName = ""
+ private var pinnedSslIssuedToUName = ""
+ private var pinnedSslIssuedByCName = ""
+ private var pinnedSslIssuedByOName = ""
+ private var pinnedSslIssuedByUName = ""
+ private var pinnedSslStartDate = Date(0)
+ private var pinnedSslEndDate = Date(0)
+ private val resourceRequests = Collections.synchronizedList(ArrayList<Array<String>>()) // Using a synchronized list makes adding resource requests thread safe.
+ private var blockedRequests = 0
+ private var easyListBlockedRequests = 0
+ private var easyPrivacyBlockedRequests = 0
+ private var fanboysAnnoyanceListBlockedRequests = 0
+ private var fanboysSocialBlockingListBlockedRequests = 0
+ private var ultraListBlockedRequests = 0
+ private var ultraPrivacyBlockedRequests = 0
+ private var thirdPartyBlockedRequests = 0
+
+ init {
+ // Enable nested scrolling by default.
+ nestedScrollingChildHelper.isNestedScrollingEnabled = true
+
+ // Initialize the favorite icon.
+ 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)
+
+ // Cast the favorite icon drawable to a bitmap drawable.
+ val favoriteIconBitmapDrawable = (favoriteIconDrawable as BitmapDrawable?)!!
+
+ // Store the default icon bitmap.
+ favoriteOrDefaultIcon = favoriteIconBitmapDrawable.bitmap
+ }
+
+ fun setFavoriteOrDefaultIcon(icon: Bitmap) {
+ // 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) {
+ Bitmap.createScaledBitmap(icon, 256, 256, true)
+ } else {
+ // Store the icon as presented.
+ icon
+ }
+ }
+
+ 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
+ }
+
+
+ // Reset the handlers.
+ fun resetSslErrorHandler() {
+ // Reset the current SSL error handler.
+ sslErrorHandler = null
+ }
+
+ fun resetHttpAuthHandler() {
+ // Reset the current HTTP authentication handler.
+ httpAuthHandler = null
+ }
+
+
+ // Pinned SSL certificates.
+ fun hasPinnedSslCertificate(): Boolean {
+ // Return the status of the pinned SSL certificate.
+ return hasPinnedSslCertificate
+ }
+
+ fun setPinnedSslCertificate(issuedToCName: String, issuedToOName: String, issuedToUName: String, issuedByCName: String, issuedByOName: String, issuedByUName: String, startDate: Date, endDate: Date) {
+ // Store the pinned SSL certificate information.
+ pinnedSslIssuedToCName = issuedToCName
+ pinnedSslIssuedToOName = issuedToOName
+ pinnedSslIssuedToUName = issuedToUName
+ pinnedSslIssuedByCName = issuedByCName
+ pinnedSslIssuedByOName = issuedByOName
+ pinnedSslIssuedByUName = issuedByUName
+ pinnedSslStartDate = startDate
+ pinnedSslEndDate = endDate
+
+ // Set the pinned SSL certificate tracker.
+ hasPinnedSslCertificate = true
+ }
+
+ fun getPinnedSslCertificate(): ArrayList<Any> {
+ // Initialize an array list.
+ val arrayList = ArrayList<Any>()
+
+ // 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
+ }
+
+ fun clearPinnedSslCertificate() {
+ // Clear the pinned SSL certificate.
+ pinnedSslIssuedToCName = ""
+ pinnedSslIssuedToOName = ""
+ pinnedSslIssuedToUName = ""
+ pinnedSslIssuedByCName = ""
+ pinnedSslIssuedByOName = ""
+ pinnedSslIssuedByUName = ""
+ pinnedSslStartDate = Date(0)
+ pinnedSslEndDate = Date(0)
+
+ // Clear the pinned SSL certificate tracker.
+ hasPinnedSslCertificate = false
+ }
+
+
+ // Resource requests.
+ fun addResourceRequest(resourceRequest: Array<String>) {
+ // Add the resource request to the list.
+ resourceRequests.add(resourceRequest)
+ }
+
+ fun getResourceRequests(): List<Array<String>> {
+ // Return the list of resource requests as an array list.
+ return resourceRequests
+ }
+
+ fun clearResourceRequests() {
+ // Clear the resource requests.
+ resourceRequests.clear()
+ }
+
+
+ // Resource request counters.
+ fun incrementRequestsCount(blocklist: Int) {
+ // Increment the count of the indicated blocklist.
+ when (blocklist) {
+ BLOCKED_REQUESTS -> blockedRequests++
+ EASYLIST -> easyListBlockedRequests++
+ EASYPRIVACY -> easyPrivacyBlockedRequests++
+ FANBOYS_ANNOYANCE_LIST -> fanboysAnnoyanceListBlockedRequests++
+ FANBOYS_SOCIAL_BLOCKING_LIST -> fanboysSocialBlockingListBlockedRequests++
+ ULTRALIST -> ultraListBlockedRequests++
+ ULTRAPRIVACY -> ultraPrivacyBlockedRequests++
+ THIRD_PARTY_REQUESTS -> thirdPartyBlockedRequests++
+ }
+ }
+
+ fun getRequestsCount(blocklist: Int): Int {
+ // Return the count of the indicated blocklist.
+ return when (blocklist) {
+ BLOCKED_REQUESTS -> blockedRequests
+ EASYLIST -> easyListBlockedRequests
+ EASYPRIVACY -> easyPrivacyBlockedRequests
+ FANBOYS_ANNOYANCE_LIST -> fanboysAnnoyanceListBlockedRequests
+ FANBOYS_SOCIAL_BLOCKING_LIST -> fanboysSocialBlockingListBlockedRequests
+ ULTRALIST -> ultraListBlockedRequests
+ ULTRAPRIVACY -> ultraPrivacyBlockedRequests
+ THIRD_PARTY_REQUESTS -> thirdPartyBlockedRequests
+ else -> 0 // Return 0. This should never be called, but it is required by the return when statement.
+ }
+ }
+
+ fun resetRequestsCounters() {
+ // Reset all the resource request counters.
+ blockedRequests = 0
+ easyListBlockedRequests = 0
+ easyPrivacyBlockedRequests = 0
+ fanboysAnnoyanceListBlockedRequests = 0
+ fanboysSocialBlockingListBlockedRequests = 0
+ ultraListBlockedRequests = 0
+ ultraPrivacyBlockedRequests = 0
+ thirdPartyBlockedRequests = 0
+ }
+
+
+ // Publicly expose the scroll ranges.
+ fun getHorizontalScrollRange(): Int {
+ // Return the horizontal scroll range.
+ return computeHorizontalScrollRange()
+ }
+
+ fun getVerticalScrollRange(): Int {
+ // Return the vertical scroll range.
+ return computeVerticalScrollRange()
+ }
+
+
+ // Handle touches.
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onTouchEvent(motionEvent: MotionEvent): Boolean {
+ // Run the commands for the given motion event action.
+ when (motionEvent.action) {
+ MotionEvent.ACTION_DOWN -> {
+ // Start nested scrolling along the vertical axis. `ViewCompat` must be used until the minimum API >= 21.
+ startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL)
+
+ // Save the current Y position. Action down will not be called again until a new motion starts.
+ previousYPosition = motionEvent.y.toInt()
+ }
+ MotionEvent.ACTION_MOVE -> {
+ // Get the current Y position.
+ val currentYMotionPosition = motionEvent.y.toInt()
+
+ // Calculate the pre-scroll delta Y.
+ val preScrollDeltaY = previousYPosition - currentYMotionPosition
+
+ // Initialize a variable to track how much of the scroll is consumed.
+ val consumedScroll = IntArray(2)
+
+ // Initialize a variable to track the offset in the window.
+ val offsetInWindow = IntArray(2)
+
+ // Get the WebView Y position.
+ val webViewYPosition = scrollY
+
+ // Set the scroll delta Y to initially be the same as the pre-scroll delta Y.
+ var scrollDeltaY = preScrollDeltaY
+
+ // Dispatch the nested pre-school. This scrolls the app bar if it needs it. `offsetInWindow` will be returned with an updated value.
+ if (dispatchNestedPreScroll(0, preScrollDeltaY, consumedScroll, offsetInWindow)) {
+ // Update the scroll delta Y if some of it was consumed.
+ scrollDeltaY = preScrollDeltaY - consumedScroll[1]
+ }
+
+ // Check to see if the WebView is at the top and and the scroll action is downward.
+ if (webViewYPosition == 0 && scrollDeltaY < 0) { // Swipe to refresh is being engaged.
+ // Stop the nested scroll so that swipe to refresh has complete control. This way releasing the scroll to refresh circle doesn't scroll the WebView at the same time.
+ stopNestedScroll()
+ } else { // Swipe to refresh is not being engaged.
+ // Start the nested scroll so that the app bar can scroll off the screen.
+ startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL)
+
+ // Dispatch the nested scroll. This scrolls the WebView. The delta Y unconsumed normally controls the swipe refresh layout, but that is handled with the `if` statement above.
+ dispatchNestedScroll(0, scrollDeltaY, 0, 0, offsetInWindow)
+
+ // Store the current Y position for use in the next action move.
+ previousYPosition -= scrollDeltaY
+ }
+ }
+ else -> stopNestedScroll() // Stop nested scrolling.
+ }
+
+ // Perform a click. This is required by the Android accessibility guidelines.
+ performClick()
+
+ // Run the default commands and return the result.
+ return super.onTouchEvent(motionEvent)
+ }
+
+
+ // Save and restore state.
+ fun saveNestedScrollWebViewState(): Bundle {
+ // Create a saved state bundle.
+ val savedState = Bundle()
+
+ // Populate the saved state bundle.
+ 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.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.putString(PINNED_SSL_ISSUED_BY_CNAME, pinnedSslIssuedByCName)
+ savedState.putString(PINNED_SSL_ISSUED_BY_ONAME, pinnedSslIssuedByOName)
+ savedState.putString(PINNED_SSL_ISSUED_BY_UNAME, pinnedSslIssuedByUName)
+ 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.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
+ }
+
+ fun restoreNestedScrollWebViewState(savedState: Bundle) {
+ // Restore the class variables.
+ 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)
+ 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)!!
+ pinnedSslIssuedByCName = savedState.getString(PINNED_SSL_ISSUED_BY_CNAME)!!
+ pinnedSslIssuedByOName = savedState.getString(PINNED_SSL_ISSUED_BY_ONAME)!!
+ pinnedSslIssuedByUName = savedState.getString(PINNED_SSL_ISSUED_BY_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)
+ this.settings.userAgentString = savedState.getString(USER_AGENT)
+ this.settings.useWideViewPort = savedState.getBoolean(WIDE_VIEWPORT)
+ this.settings.textZoom = savedState.getInt(FONT_SIZE)
+ }
+
+
+ // Method from NestedScrollingChild.
+ override fun setNestedScrollingEnabled(status: Boolean) {
+ // Set the status of the nested scrolling.
+ nestedScrollingChildHelper.isNestedScrollingEnabled = status
+ }
+
+ // Method from NestedScrollingChild.
+ override fun isNestedScrollingEnabled(): Boolean {
+ // Return the status of nested scrolling.
+ return nestedScrollingChildHelper.isNestedScrollingEnabled
+ }
+
+ // Method from NestedScrollingChild.
+ override fun startNestedScroll(axes: Int): Boolean {
+ // Start a nested scroll along the indicated axes.
+ return nestedScrollingChildHelper.startNestedScroll(axes)
+ }
+
+ // Method from NestedScrollingChild2.
+ override fun startNestedScroll(axes: Int, type: Int): Boolean {
+ // Start a nested scroll along the indicated axes for the given type of input which caused the scroll event.
+ return nestedScrollingChildHelper.startNestedScroll(axes, type)
+ }
+
+ // Method from NestedScrollingChild.
+ override fun stopNestedScroll() {
+ // Stop the nested scroll.
+ nestedScrollingChildHelper.stopNestedScroll()
+ }
+
+ // Method from NestedScrollingChild2.
+ override fun stopNestedScroll(type: Int) {
+ // Stop the nested scroll of the given type of input which caused the scroll event.
+ nestedScrollingChildHelper.stopNestedScroll(type)
+ }
+
+ // Method from NestedScrollingChild.
+ override fun hasNestedScrollingParent(): Boolean {
+ // Return the status of the nested scrolling parent.
+ return nestedScrollingChildHelper.hasNestedScrollingParent()
+ }
+
+ // Method from NestedScrollingChild2.
+ override fun hasNestedScrollingParent(type: Int): Boolean {
+ // return the status of the nested scrolling parent for the given type of input which caused the scroll event.
+ return nestedScrollingChildHelper.hasNestedScrollingParent(type)
+ }
+
+ // Method from NestedScrollingChild.
+ override fun dispatchNestedPreScroll(deltaX: Int, deltaY: Int, consumed: IntArray?, offsetInWindow: IntArray?): Boolean {
+ // Dispatch a nested pre-scroll with the specified deltas, which lets a parent to consume some of the scroll if desired.
+ return nestedScrollingChildHelper.dispatchNestedPreScroll(deltaX, deltaY, consumed, offsetInWindow)
+ }
+
+ // Method from NestedScrollingChild2.
+ override fun dispatchNestedPreScroll(deltaX: Int, deltaY: Int, consumed: IntArray?, offsetInWindow: IntArray?, type: Int): Boolean {
+ // Dispatch a nested pre-scroll with the specified deltas for the given type of input which caused the scroll event, which lets a parent to consume some of the scroll if desired.
+ return nestedScrollingChildHelper.dispatchNestedPreScroll(deltaX, deltaY, consumed, offsetInWindow, type)
+ }
+
+ // Method from NestedScrollingChild.
+ override fun dispatchNestedScroll(deltaXConsumed: Int, deltaYConsumed: Int, deltaXUnconsumed: Int, deltaYUnconsumed: Int, offsetInWindow: IntArray?): Boolean {
+ // Dispatch a nested scroll with the specified deltas.
+ return nestedScrollingChildHelper.dispatchNestedScroll(deltaXConsumed, deltaYConsumed, deltaXUnconsumed, deltaYUnconsumed, offsetInWindow)
+ }
+
+ // Method from NestedScrollingChild2.
+ override fun dispatchNestedScroll(deltaXConsumed: Int, deltaYConsumed: Int, deltaXUnconsumed: Int, deltaYUnconsumed: Int, offsetInWindow: IntArray?, type: Int): Boolean {
+ // Dispatch a nested scroll with the specified deltas for the given type of input which caused the scroll event.
+ return nestedScrollingChildHelper.dispatchNestedScroll(deltaXConsumed, deltaYConsumed, deltaXUnconsumed, deltaYUnconsumed, offsetInWindow, type)
+ }
+
+ // Method from NestedScrollingChild.
+ override fun dispatchNestedPreFling(velocityX: Float, velocityY: Float): Boolean {
+ // Dispatch a nested pre-fling with the specified velocity, which lets a parent consume the fling if desired.
+ return nestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY)
+ }
+
+ // Method from NestedScrollingChild.
+ override fun dispatchNestedFling(velocityX: Float, velocityY: Float, consumed: Boolean): Boolean {
+ // Dispatch a nested fling with the specified velocity.
+ return nestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed)
+ }
+}
\ No newline at end of file