X-Git-Url: https://gitweb.stoutner.com/?a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fstoutner%2Fprivacybrowser%2Fviews%2FNestedScrollWebView.kt;fp=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fstoutner%2Fprivacybrowser%2Fviews%2FNestedScrollWebView.kt;h=2900e0ab8c2d63e0b7129fc9fd877bcdeedb9234;hb=6ea3e4ebd779594b45ad8f51cc40761bb6d89030;hp=0000000000000000000000000000000000000000;hpb=bda37dc9784e900cb64b87af3e221e11320d9d01;p=PrivacyBrowserAndroid.git diff --git a/app/src/main/java/com/stoutner/privacybrowser/views/NestedScrollWebView.kt b/app/src/main/java/com/stoutner/privacybrowser/views/NestedScrollWebView.kt new file mode 100644 index 00000000..2900e0ab --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/views/NestedScrollWebView.kt @@ -0,0 +1,536 @@ +/* + * Copyright © 2019-2021 Soren Stoutner . + * + * This file is part of 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 . + */ + +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>()) // 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 { + // Initialize an array list. + val arrayList = ArrayList() + + // 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) { + // Add the resource request to the list. + resourceRequests.add(resourceRequest) + } + + fun getResourceRequests(): List> { + // 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