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