]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/views/NestedScrollWebView.kt
Fix the WebView layout with bottom app bars. https://redmine.stoutner.com/issues/839
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / views / NestedScrollWebView.kt
1 /*
2  * Copyright © 2019-2022 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
5  *
6  * Privacy Browser Android is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Privacy Browser Android is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Privacy Browser Android.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 package com.stoutner.privacybrowser.views
21
22 import android.animation.ObjectAnimator
23 import android.annotation.SuppressLint
24 import android.content.Context
25 import android.graphics.Bitmap
26 import android.graphics.drawable.BitmapDrawable
27 import android.os.Bundle
28 import android.util.AttributeSet
29 import android.view.MotionEvent
30 import android.webkit.HttpAuthHandler
31 import android.webkit.SslErrorHandler
32 import android.webkit.WebView
33
34 import androidx.core.content.ContextCompat
35 import androidx.core.view.NestedScrollingChild2
36 import androidx.core.view.NestedScrollingChildHelper
37 import androidx.core.view.ViewCompat
38
39 import com.stoutner.privacybrowser.R
40 import com.stoutner.privacybrowser.activities.MainWebViewActivity
41
42 import java.util.Collections
43 import java.util.Date
44
45 import kotlin.collections.ArrayList
46
47 import kotlin.jvm.JvmOverloads
48
49 // Define the saved state constants.
50 private const val DOMAIN_SETTINGS_APPLIED = "domain_settings_applied"
51 private const val DOMAIN_SETTINGS_DATABASE_ID = "domain_settings_database_id"
52 private const val CURRENT_DOMAIN_NAME = "current_domain_name"
53 private const val CURRENT_URl = "current_url"
54 private const val ACCEPT_COOKIES = "accept_cookies"
55 private const val EASYLIST_ENABLED = "easylist_enabled"
56 private const val EASYPRIVACY_ENABLED = "easyprivacy_enabled"
57 private const val FANBOYS_ANNOYANCE_LIST_ENABLED = "fanboys_annoyance_list_enabled"
58 private const val FANBOYS_SOCIAL_BLOCKING_LIST_ENABLED = "fanboys_social_blocking_list_enabled"
59 private const val ULTRALIST_ENABLED = "ultralist_enabled"
60 private const val ULTRAPRIVACY_ENABLED = "ultraprivacy_enabled"
61 private const val BLOCK_ALL_THIRD_PARTY_REQUESTS = "block_all_third_party_requests"
62 private const val HAS_PINNED_SSL_CERTIFICATE = "has_pinned_ssl_certificate"
63 private const val PINNED_SSL_ISSUED_TO_CNAME = "pinned_ssl_issued_to_cname"
64 private const val PINNED_SSL_ISSUED_TO_ONAME = "pinned_ssl_issued_to_oname"
65 private const val PINNED_SSL_ISSUED_TO_UNAME = "pinned_ssl_issued_to_uname"
66 private const val PINNED_SSL_ISSUED_BY_CNAME = "pinned_ssl_issued_by_cname"
67 private const val PINNED_SSL_ISSUED_BY_ONAME = "pinned_ssl_issued_by_oname"
68 private const val PINNED_SSL_ISSUED_BY_UNAME = "pinned_ssl_issued_by_uname"
69 private const val PINNED_SSL_START_DATE = "pinned_ssl_start_date"
70 private const val PINNED_SSL_END_DATE = "pinned_ssl_end_date"
71 private const val PINNED_IP_ADDRESSES = "pinned_ip_addresses"
72 private const val IGNORE_PINNED_DOMAIN_INFORMATION = "ignore_pinned_domain_information"
73 private const val SWIPE_TO_REFRESH = "swipe_to_refresh"
74 private const val JAVASCRIPT_ENABLED = "javascript_enabled"
75 private const val DOM_STORAGE_ENABLED = "dom_storage_enabled"
76 private const val USER_AGENT = "user_agent"
77 private const val WIDE_VIEWPORT = "wide_viewport"
78 private const val FONT_SIZE = "font_size"
79
80 // 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.
81 class NestedScrollWebView @JvmOverloads constructor(context: Context, attributeSet: AttributeSet? = null, defaultStyle: Int = android.R.attr.webViewStyle) : WebView(context, attributeSet, defaultStyle),
82     NestedScrollingChild2 {
83     companion object {
84         // Define the public companion object constants.  These can be moved to public class constants once the entire project has migrated to Kotlin.
85         const val BLOCKED_REQUESTS = 0
86         const val EASYLIST = 1
87         const val EASYPRIVACY = 2
88         const val FANBOYS_ANNOYANCE_LIST = 3
89         const val FANBOYS_SOCIAL_BLOCKING_LIST = 4
90         const val ULTRALIST = 5
91         const val ULTRAPRIVACY = 6
92         const val THIRD_PARTY_REQUESTS = 7
93     }
94
95     // Define the public variables.
96     var acceptCookies = false
97     var blockAllThirdPartyRequests = false
98     var currentDomainName = ""
99     var currentIpAddresses = ""
100     var currentUrl = ""
101     var domainSettingsApplied = false
102     var domainSettingsDatabaseId = 0
103     var easyListEnabled = true
104     var easyPrivacyEnabled = true
105     var fanboysAnnoyanceListEnabled = true
106     var fanboysSocialBlockingListEnabled = true
107     var httpAuthHandler: HttpAuthHandler? = null
108     var ignorePinnedDomainInformation = false
109     var pinnedIpAddresses = ""
110     var sslErrorHandler: SslErrorHandler? = null
111     var swipeToRefresh = false
112     var ultraListEnabled = true
113     var ultraPrivacyEnabled = true
114     var waitingForProxyUrlString = ""
115     var webViewFragmentId: Long = 0
116
117
118     // Define the private variables.
119     private val nestedScrollingChildHelper: NestedScrollingChildHelper = NestedScrollingChildHelper(this)
120     private lateinit var favoriteOrDefaultIcon: Bitmap
121     private var previousYPosition = 0  // The previous Y position needs to be tracked between motion events.
122     private var hasPinnedSslCertificate = false
123     private var pinnedSslIssuedToCName = ""
124     private var pinnedSslIssuedToOName = ""
125     private var pinnedSslIssuedToUName = ""
126     private var pinnedSslIssuedByCName = ""
127     private var pinnedSslIssuedByOName = ""
128     private var pinnedSslIssuedByUName = ""
129     private var pinnedSslStartDate = Date(0)
130     private var pinnedSslEndDate = Date(0)
131     private val resourceRequests = Collections.synchronizedList(ArrayList<Array<String>>())  // Using a synchronized list makes adding resource requests thread safe.
132     private var blockedRequests = 0
133     private var easyListBlockedRequests = 0
134     private var easyPrivacyBlockedRequests = 0
135     private var fanboysAnnoyanceListBlockedRequests = 0
136     private var fanboysSocialBlockingListBlockedRequests = 0
137     private var ultraListBlockedRequests = 0
138     private var ultraPrivacyBlockedRequests = 0
139     private var thirdPartyBlockedRequests = 0
140
141     init {
142         // Enable nested scrolling by default.
143         nestedScrollingChildHelper.isNestedScrollingEnabled = true
144
145         // Initialize the favorite icon.
146         initializeFavoriteIcon()
147     }
148
149
150     // Favorite or default icon.
151     fun initializeFavoriteIcon() {
152         // Get the default favorite icon drawable.  `ContextCompat` must be used until API >= 21.
153         val favoriteIconDrawable = ContextCompat.getDrawable(context, R.drawable.world)
154
155         // Cast the favorite icon drawable to a bitmap drawable.
156         val favoriteIconBitmapDrawable = (favoriteIconDrawable as BitmapDrawable?)!!
157
158         // Store the default icon bitmap.
159         favoriteOrDefaultIcon = favoriteIconBitmapDrawable.bitmap
160     }
161
162     fun setFavoriteOrDefaultIcon(icon: Bitmap) {
163         // Scale the favorite icon bitmap down if it is larger than 256 x 256.  Filtering uses bilinear interpolation.
164         favoriteOrDefaultIcon = if (icon.height > 256 || icon.width > 256) {
165             Bitmap.createScaledBitmap(icon, 256, 256, true)
166         } else {
167             // Store the icon as presented.
168             icon
169         }
170     }
171
172     fun getFavoriteOrDefaultIcon(): Bitmap {
173         // 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.
174         return favoriteOrDefaultIcon
175     }
176
177
178     // Reset the handlers.
179     fun resetSslErrorHandler() {
180         // Reset the current SSL error handler.
181         sslErrorHandler = null
182     }
183
184     fun resetHttpAuthHandler() {
185         // Reset the current HTTP authentication handler.
186         httpAuthHandler = null
187     }
188
189
190     // Pinned SSL certificates.
191     fun hasPinnedSslCertificate(): Boolean {
192         // Return the status of the pinned SSL certificate.
193         return hasPinnedSslCertificate
194     }
195
196     fun setPinnedSslCertificate(issuedToCName: String, issuedToOName: String, issuedToUName: String, issuedByCName: String, issuedByOName: String, issuedByUName: String, startDate: Date, endDate: Date) {
197         // Store the pinned SSL certificate information.
198         pinnedSslIssuedToCName = issuedToCName
199         pinnedSslIssuedToOName = issuedToOName
200         pinnedSslIssuedToUName = issuedToUName
201         pinnedSslIssuedByCName = issuedByCName
202         pinnedSslIssuedByOName = issuedByOName
203         pinnedSslIssuedByUName = issuedByUName
204         pinnedSslStartDate = startDate
205         pinnedSslEndDate = endDate
206
207         // Set the pinned SSL certificate tracker.
208         hasPinnedSslCertificate = true
209     }
210
211     fun getPinnedSslCertificate(): Pair<Array<String>, Array<Date>> {
212         // Create the SSL certificate string array.
213         val sslCertificateStringArray = arrayOf(pinnedSslIssuedToCName, pinnedSslIssuedToOName, pinnedSslIssuedToUName, pinnedSslIssuedByCName, pinnedSslIssuedByOName, pinnedSslIssuedByUName)
214
215         // Create the SSL certificate date array.
216         val sslCertificateDateArray = arrayOf(pinnedSslStartDate, pinnedSslEndDate)
217
218         // Return the pinned SSL certificate pair.
219         return Pair(sslCertificateStringArray, sslCertificateDateArray)
220     }
221
222     fun clearPinnedSslCertificate() {
223         // Clear the pinned SSL certificate.
224         pinnedSslIssuedToCName = ""
225         pinnedSslIssuedToOName = ""
226         pinnedSslIssuedToUName = ""
227         pinnedSslIssuedByCName = ""
228         pinnedSslIssuedByOName = ""
229         pinnedSslIssuedByUName = ""
230         pinnedSslStartDate = Date(0)
231         pinnedSslEndDate = Date(0)
232
233         // Clear the pinned SSL certificate tracker.
234         hasPinnedSslCertificate = false
235     }
236
237
238     // Resource requests.
239     fun addResourceRequest(resourceRequest: Array<String>) {
240         // Add the resource request to the list.
241         resourceRequests.add(resourceRequest)
242     }
243
244     fun getResourceRequests(): List<Array<String>> {
245         // Return the list of resource requests as an array list.
246         return resourceRequests
247     }
248
249     fun clearResourceRequests() {
250         // Clear the resource requests.
251         resourceRequests.clear()
252     }
253
254
255     // Resource request counters.
256     fun incrementRequestsCount(blocklist: Int) {
257         // Increment the count of the indicated blocklist.
258         when (blocklist) {
259             BLOCKED_REQUESTS -> blockedRequests++
260             EASYLIST -> easyListBlockedRequests++
261             EASYPRIVACY -> easyPrivacyBlockedRequests++
262             FANBOYS_ANNOYANCE_LIST -> fanboysAnnoyanceListBlockedRequests++
263             FANBOYS_SOCIAL_BLOCKING_LIST -> fanboysSocialBlockingListBlockedRequests++
264             ULTRALIST -> ultraListBlockedRequests++
265             ULTRAPRIVACY -> ultraPrivacyBlockedRequests++
266             THIRD_PARTY_REQUESTS -> thirdPartyBlockedRequests++
267         }
268     }
269
270     fun getRequestsCount(blocklist: Int): Int {
271         // Return the count of the indicated blocklist.
272         return when (blocklist) {
273             BLOCKED_REQUESTS -> blockedRequests
274             EASYLIST -> easyListBlockedRequests
275             EASYPRIVACY -> easyPrivacyBlockedRequests
276             FANBOYS_ANNOYANCE_LIST -> fanboysAnnoyanceListBlockedRequests
277             FANBOYS_SOCIAL_BLOCKING_LIST -> fanboysSocialBlockingListBlockedRequests
278             ULTRALIST -> ultraListBlockedRequests
279             ULTRAPRIVACY -> ultraPrivacyBlockedRequests
280             THIRD_PARTY_REQUESTS -> thirdPartyBlockedRequests
281             else -> 0 // Return 0.  This should never be called, but it is required by the return when statement.
282         }
283     }
284
285     fun resetRequestsCounters() {
286         // Reset all the resource request counters.
287         blockedRequests = 0
288         easyListBlockedRequests = 0
289         easyPrivacyBlockedRequests = 0
290         fanboysAnnoyanceListBlockedRequests = 0
291         fanboysSocialBlockingListBlockedRequests = 0
292         ultraListBlockedRequests = 0
293         ultraPrivacyBlockedRequests = 0
294         thirdPartyBlockedRequests = 0
295     }
296
297
298     // Publicly expose the scroll ranges.
299     fun getHorizontalScrollRange(): Int {
300         // Return the horizontal scroll range.
301         return computeHorizontalScrollRange()
302     }
303
304     fun getVerticalScrollRange(): Int {
305         // Return the vertical scroll range.
306         return computeVerticalScrollRange()
307     }
308
309     override fun onOverScrolled(scrollX: Int, scrollY: Int, clampedX: Boolean, clampedY: Boolean) {
310         // Run the default commands.
311         super.onOverScrolled(scrollX, scrollY, clampedX, clampedY)
312
313         // Display the bottom app bar if it has been hidden and the WebView was over-scrolled at the top of the screen.
314         if ((MainWebViewActivity.appBarLayout.translationY != 0f) && (scrollY == 0) && clampedY) {
315             // Animate the bottom app bar onto the screen.
316             val objectAnimator = ObjectAnimator.ofFloat(MainWebViewActivity.appBarLayout, "translationY", 0f)
317
318             // Make it so.
319             objectAnimator.start()
320         }
321     }
322
323     // Handle touches.
324     @SuppressLint("ClickableViewAccessibility")
325     override fun onTouchEvent(motionEvent: MotionEvent): Boolean {
326         // Run the commands for the given motion event action.
327         when (motionEvent.action) {
328             MotionEvent.ACTION_DOWN -> {
329                 // Start nested scrolling along the vertical axis.  `ViewCompat` must be used until the minimum API >= 21.
330                 startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL)
331
332                 // Save the current Y position.  Action down will not be called again until a new motion starts.
333                 previousYPosition = motionEvent.y.toInt()
334             }
335             MotionEvent.ACTION_MOVE -> {
336                 // Get the current Y position.
337                 val currentYMotionPosition = motionEvent.y.toInt()
338
339                 // Calculate the pre-scroll delta Y.
340                 val preScrollDeltaY = previousYPosition - currentYMotionPosition
341
342                 // Initialize a variable to track how much of the scroll is consumed.
343                 val consumedScroll = IntArray(2)
344
345                 // Initialize a variable to track the offset in the window.
346                 val offsetInWindow = IntArray(2)
347
348                 // Get the WebView Y position.
349                 val webViewYPosition = scrollY
350
351                 // Set the scroll delta Y to initially be the same as the pre-scroll delta Y.
352                 var scrollDeltaY = preScrollDeltaY
353
354                 // Dispatch the nested pre-school.  This scrolls the app bar if it needs it.  `offsetInWindow` will be returned with an updated value.
355                 if (dispatchNestedPreScroll(0, preScrollDeltaY, consumedScroll, offsetInWindow)) {
356                     // Update the scroll delta Y if some of it was consumed.
357                     scrollDeltaY = preScrollDeltaY - consumedScroll[1]
358                 }
359
360                 // Check to see if the WebView is at the top and and the scroll action is downward.
361                 if (webViewYPosition == 0 && scrollDeltaY < 0) {  // Swipe to refresh is being engaged.
362                     // 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.
363                     stopNestedScroll()
364                 } else {  // Swipe to refresh is not being engaged.
365                     // Start the nested scroll so that the app bar can scroll off the screen.
366                     startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL)
367
368                     // 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.
369                     dispatchNestedScroll(0, scrollDeltaY, 0, 0, offsetInWindow)
370
371                     // Store the current Y position for use in the next action move.
372                     previousYPosition -= scrollDeltaY
373                 }
374             }
375             else -> stopNestedScroll()  // Stop nested scrolling.
376         }
377
378         // Perform a click.  This is required by the Android accessibility guidelines.
379         performClick()
380
381         // Run the default commands and return the result.
382         return super.onTouchEvent(motionEvent)
383     }
384
385
386     // Save and restore state.
387     fun saveNestedScrollWebViewState(): Bundle {
388         // Create a saved state bundle.
389         val savedState = Bundle()
390
391         // Populate the saved state bundle.
392         savedState.putBoolean(DOMAIN_SETTINGS_APPLIED, domainSettingsApplied)
393         savedState.putInt(DOMAIN_SETTINGS_DATABASE_ID, domainSettingsDatabaseId)
394         savedState.putString(CURRENT_DOMAIN_NAME, currentDomainName)
395         savedState.putString(CURRENT_URl, currentUrl)
396         savedState.putBoolean(ACCEPT_COOKIES, acceptCookies)
397         savedState.putBoolean(EASYLIST_ENABLED, easyListEnabled)
398         savedState.putBoolean(EASYPRIVACY_ENABLED, easyPrivacyEnabled)
399         savedState.putBoolean(FANBOYS_ANNOYANCE_LIST_ENABLED, fanboysAnnoyanceListEnabled)
400         savedState.putBoolean(FANBOYS_SOCIAL_BLOCKING_LIST_ENABLED, fanboysSocialBlockingListEnabled)
401         savedState.putBoolean(ULTRALIST_ENABLED, ultraListEnabled)
402         savedState.putBoolean(ULTRAPRIVACY_ENABLED, ultraPrivacyEnabled)
403         savedState.putBoolean(BLOCK_ALL_THIRD_PARTY_REQUESTS, blockAllThirdPartyRequests)
404         savedState.putBoolean(HAS_PINNED_SSL_CERTIFICATE, hasPinnedSslCertificate)
405         savedState.putString(PINNED_SSL_ISSUED_TO_CNAME, pinnedSslIssuedToCName)
406         savedState.putString(PINNED_SSL_ISSUED_TO_ONAME, pinnedSslIssuedToOName)
407         savedState.putString(PINNED_SSL_ISSUED_TO_UNAME, pinnedSslIssuedToUName)
408         savedState.putString(PINNED_SSL_ISSUED_BY_CNAME, pinnedSslIssuedByCName)
409         savedState.putString(PINNED_SSL_ISSUED_BY_ONAME, pinnedSslIssuedByOName)
410         savedState.putString(PINNED_SSL_ISSUED_BY_UNAME, pinnedSslIssuedByUName)
411         savedState.putLong(PINNED_SSL_START_DATE, pinnedSslStartDate.time)
412         savedState.putLong(PINNED_SSL_END_DATE, pinnedSslEndDate.time)
413         savedState.putString(PINNED_IP_ADDRESSES, pinnedIpAddresses)
414         savedState.putBoolean(IGNORE_PINNED_DOMAIN_INFORMATION, ignorePinnedDomainInformation)
415         savedState.putBoolean(SWIPE_TO_REFRESH, swipeToRefresh)
416         savedState.putBoolean(JAVASCRIPT_ENABLED, this.settings.javaScriptEnabled)
417         savedState.putBoolean(DOM_STORAGE_ENABLED, this.settings.domStorageEnabled)
418         savedState.putString(USER_AGENT, this.settings.userAgentString)
419         savedState.putBoolean(WIDE_VIEWPORT, this.settings.useWideViewPort)
420         savedState.putInt(FONT_SIZE, this.settings.textZoom)
421
422         // Return the saved state bundle.
423         return savedState
424     }
425
426     fun restoreNestedScrollWebViewState(savedState: Bundle) {
427         // Restore the class variables.
428         domainSettingsApplied = savedState.getBoolean(DOMAIN_SETTINGS_APPLIED)
429         domainSettingsDatabaseId = savedState.getInt(DOMAIN_SETTINGS_DATABASE_ID)
430         currentDomainName = savedState.getString(CURRENT_DOMAIN_NAME)!!
431         currentUrl = savedState.getString(CURRENT_URl)!!
432         acceptCookies = savedState.getBoolean(ACCEPT_COOKIES)
433         easyListEnabled = savedState.getBoolean(EASYLIST_ENABLED)
434         easyPrivacyEnabled = savedState.getBoolean(EASYPRIVACY_ENABLED)
435         fanboysAnnoyanceListEnabled = savedState.getBoolean(FANBOYS_ANNOYANCE_LIST_ENABLED)
436         fanboysSocialBlockingListEnabled = savedState.getBoolean(FANBOYS_SOCIAL_BLOCKING_LIST_ENABLED)
437         ultraListEnabled = savedState.getBoolean(ULTRALIST_ENABLED)
438         ultraPrivacyEnabled = savedState.getBoolean(ULTRAPRIVACY_ENABLED)
439         blockAllThirdPartyRequests = savedState.getBoolean(BLOCK_ALL_THIRD_PARTY_REQUESTS)
440         hasPinnedSslCertificate = savedState.getBoolean(HAS_PINNED_SSL_CERTIFICATE)
441         pinnedSslIssuedToCName = savedState.getString(PINNED_SSL_ISSUED_TO_CNAME)!!
442         pinnedSslIssuedToOName = savedState.getString(PINNED_SSL_ISSUED_TO_ONAME)!!
443         pinnedSslIssuedToUName = savedState.getString(PINNED_SSL_ISSUED_TO_UNAME)!!
444         pinnedSslIssuedByCName = savedState.getString(PINNED_SSL_ISSUED_BY_CNAME)!!
445         pinnedSslIssuedByOName = savedState.getString(PINNED_SSL_ISSUED_BY_ONAME)!!
446         pinnedSslIssuedByUName = savedState.getString(PINNED_SSL_ISSUED_BY_UNAME)!!
447         pinnedSslStartDate = Date(savedState.getLong(PINNED_SSL_START_DATE))
448         pinnedSslEndDate = Date(savedState.getLong(PINNED_SSL_END_DATE))
449         pinnedIpAddresses = savedState.getString(PINNED_IP_ADDRESSES)!!
450         ignorePinnedDomainInformation = savedState.getBoolean(IGNORE_PINNED_DOMAIN_INFORMATION)
451         swipeToRefresh = savedState.getBoolean(SWIPE_TO_REFRESH)
452         this.settings.javaScriptEnabled = savedState.getBoolean(JAVASCRIPT_ENABLED)
453         this.settings.domStorageEnabled = savedState.getBoolean(DOM_STORAGE_ENABLED)
454         this.settings.userAgentString = savedState.getString(USER_AGENT)
455         this.settings.useWideViewPort = savedState.getBoolean(WIDE_VIEWPORT)
456         this.settings.textZoom = savedState.getInt(FONT_SIZE)
457     }
458
459
460     // Method from NestedScrollingChild.
461     override fun setNestedScrollingEnabled(status: Boolean) {
462         // Set the status of the nested scrolling.
463         nestedScrollingChildHelper.isNestedScrollingEnabled = status
464     }
465
466     // Method from NestedScrollingChild.
467     override fun isNestedScrollingEnabled(): Boolean {
468         // Return the status of nested scrolling.
469         return nestedScrollingChildHelper.isNestedScrollingEnabled
470     }
471
472     // Method from NestedScrollingChild.
473     override fun startNestedScroll(axes: Int): Boolean {
474         // Start a nested scroll along the indicated axes.
475         return nestedScrollingChildHelper.startNestedScroll(axes)
476     }
477
478     // Method from NestedScrollingChild2.
479     override fun startNestedScroll(axes: Int, type: Int): Boolean {
480         // Start a nested scroll along the indicated axes for the given type of input which caused the scroll event.
481         return nestedScrollingChildHelper.startNestedScroll(axes, type)
482     }
483
484     // Method from NestedScrollingChild.
485     override fun stopNestedScroll() {
486         // Stop the nested scroll.
487         nestedScrollingChildHelper.stopNestedScroll()
488     }
489
490     // Method from NestedScrollingChild2.
491     override fun stopNestedScroll(type: Int) {
492         // Stop the nested scroll of the given type of input which caused the scroll event.
493         nestedScrollingChildHelper.stopNestedScroll(type)
494     }
495
496     // Method from NestedScrollingChild.
497     override fun hasNestedScrollingParent(): Boolean {
498         // Return the status of the nested scrolling parent.
499         return nestedScrollingChildHelper.hasNestedScrollingParent()
500     }
501
502     // Method from NestedScrollingChild2.
503     override fun hasNestedScrollingParent(type: Int): Boolean {
504         // return the status of the nested scrolling parent for the given type of input which caused the scroll event.
505         return nestedScrollingChildHelper.hasNestedScrollingParent(type)
506     }
507
508     // Method from NestedScrollingChild.
509     override fun dispatchNestedPreScroll(deltaX: Int, deltaY: Int, consumed: IntArray?, offsetInWindow: IntArray?): Boolean {
510         // Dispatch a nested pre-scroll with the specified deltas, which lets a parent to consume some of the scroll if desired.
511         return nestedScrollingChildHelper.dispatchNestedPreScroll(deltaX, deltaY, consumed, offsetInWindow)
512     }
513
514     // Method from NestedScrollingChild2.
515     override fun dispatchNestedPreScroll(deltaX: Int, deltaY: Int, consumed: IntArray?, offsetInWindow: IntArray?, type: Int): Boolean {
516         // 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.
517         return nestedScrollingChildHelper.dispatchNestedPreScroll(deltaX, deltaY, consumed, offsetInWindow, type)
518     }
519
520     // Method from NestedScrollingChild.
521     override fun dispatchNestedScroll(deltaXConsumed: Int, deltaYConsumed: Int, deltaXUnconsumed: Int, deltaYUnconsumed: Int, offsetInWindow: IntArray?): Boolean {
522         // Dispatch a nested scroll with the specified deltas.
523         return nestedScrollingChildHelper.dispatchNestedScroll(deltaXConsumed, deltaYConsumed, deltaXUnconsumed, deltaYUnconsumed, offsetInWindow)
524     }
525
526     // Method from NestedScrollingChild2.
527     override fun dispatchNestedScroll(deltaXConsumed: Int, deltaYConsumed: Int, deltaXUnconsumed: Int, deltaYUnconsumed: Int, offsetInWindow: IntArray?, type: Int): Boolean {
528         // Dispatch a nested scroll with the specified deltas for the given type of input which caused the scroll event.
529         return nestedScrollingChildHelper.dispatchNestedScroll(deltaXConsumed, deltaYConsumed, deltaXUnconsumed, deltaYUnconsumed, offsetInWindow, type)
530     }
531
532     // Method from NestedScrollingChild.
533     override fun dispatchNestedPreFling(velocityX: Float, velocityY: Float): Boolean {
534         // Dispatch a nested pre-fling with the specified velocity, which lets a parent consume the fling if desired.
535         return nestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY)
536     }
537
538     // Method from NestedScrollingChild.
539     override fun dispatchNestedFling(velocityX: Float, velocityY: Float, consumed: Boolean): Boolean {
540         // Dispatch a nested fling with the specified velocity.
541         return nestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed)
542     }
543 }