280532592218b3c78c5a2e13efbcb3be40673808
[PrivacyBrowser.git] / app / src / main / java / com / stoutner / privacybrowser / views / NestedScrollWebView.java
1 /*
2  * Copyright © 2019 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
5  *
6  * Privacy Browser 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 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.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 package com.stoutner.privacybrowser.views;
21
22 import android.content.Context;
23 import android.graphics.Bitmap;
24 import android.graphics.drawable.BitmapDrawable;
25 import android.graphics.drawable.Drawable;
26 import android.util.AttributeSet;
27 import android.view.MotionEvent;
28 import android.webkit.HttpAuthHandler;
29 import android.webkit.SslErrorHandler;
30 import android.webkit.WebView;
31
32 import androidx.annotation.NonNull;
33 import androidx.core.content.ContextCompat;
34 import androidx.core.view.NestedScrollingChild2;
35 import androidx.core.view.NestedScrollingChildHelper;
36 import androidx.core.view.ViewCompat;
37
38 import com.stoutner.privacybrowser.R;
39
40 import java.util.ArrayList;
41 import java.util.Date;
42
43 // NestedScrollWebView extends WebView to handle nested scrolls (scrolling the app bar off the screen).
44 public class NestedScrollWebView extends WebView implements NestedScrollingChild2 {
45     // These constants identify the blocklists.
46     public final static int BLOCKED_REQUESTS = 0;
47     public final static int EASY_LIST = 1;
48     public final static int EASY_PRIVACY = 2;
49     public final static int FANBOYS_ANNOYANCE_LIST = 3;
50     public final static int FANBOYS_SOCIAL_BLOCKING_LIST = 4;
51     public final static int ULTRA_PRIVACY = 5;
52     public final static int THIRD_PARTY_REQUESTS = 6;
53
54     // Keep a copy of the WebView fragment ID.
55     private long webViewFragmentId;
56
57     // Store the handlers.
58     private SslErrorHandler sslErrorHandler;
59     private HttpAuthHandler httpAuthHandler;
60
61     // Track if domain settings are applied to this nested scroll WebView and, if so, the database ID.
62     private boolean domainSettingsApplied;
63     private int domainSettingsDatabaseId;
64
65     // Keep track of when the domain name changes so that domain settings can be reapplied.  This should never be null.
66     private String currentDomainName = "";
67
68     // Track the status of first-party cookies.
69     private boolean acceptFirstPartyCookies;
70
71     // Track the domain settings JavaScript status.  This can be removed once night mode does not require JavaScript.
72     private boolean domainSettingsJavaScriptEnabled;
73
74     // Track the resource requests.
75     private ArrayList<String[]> resourceRequests = new ArrayList<>();
76     private boolean easyListEnabled;
77     private boolean easyPrivacyEnabled;
78     private boolean fanboysAnnoyanceListEnabled;
79     private boolean fanboysSocialBlockingListEnabled;
80     private boolean ultraPrivacyEnabled;
81     private boolean blockAllThirdPartyRequests;
82     private int blockedRequests;
83     private int easyListBlockedRequests;
84     private int easyPrivacyBlockedRequests;
85     private int fanboysAnnoyanceListBlockedRequests;
86     private int fanboysSocialBlockingListBlockedRequests;
87     private int ultraPrivacyBlockedRequests;
88     private int thirdPartyBlockedRequests;
89
90     // The pinned SSL certificate variables.
91     private boolean hasPinnedSslCertificate;
92     private String pinnedSslIssuedToCName;
93     private String pinnedSslIssuedToOName;
94     private String pinnedSslIssuedToUName;
95     private String pinnedSslIssuedByCName;
96     private String pinnedSslIssuedByOName;
97     private String pinnedSslIssuedByUName;
98     private Date pinnedSslStartDate;
99     private Date pinnedSslEndDate;
100
101     // The current IP addresses variables.
102     private boolean hasCurrentIpAddresses;
103     private String currentIpAddresses;
104
105     // The pinned IP addresses variables.
106     private boolean hasPinnedIpAddresses;
107     private String pinnedIpAddresses;
108
109     // The ignore pinned domain information tracker.  This is set when a user proceeds past a pinned mismatch dialog to prevent the dialog from showing again until after the domain changes.
110     private boolean ignorePinnedDomainInformation;
111
112     // Track navigation of history.
113     private boolean navigatingHistory;
114
115     // The default or favorite icon.
116     private Bitmap favoriteOrDefaultIcon;
117
118     // Track night mode.
119     private boolean nightMode;
120
121     // Track swipe to refresh.
122     private boolean swipeToRefresh;
123
124     // The nested scrolling child helper is used throughout the class.
125     private NestedScrollingChildHelper nestedScrollingChildHelper;
126
127     // The previous Y position needs to be tracked between motion events.
128     private int previousYPosition;
129
130
131
132     // The basic constructor.
133     public NestedScrollWebView(Context context) {
134         // Roll up to the next constructor.
135         this(context, null);
136     }
137
138     // The intermediate constructor.
139     public NestedScrollWebView(Context context, AttributeSet attributeSet) {
140         // Roll up to the next constructor.
141         this(context, attributeSet, android.R.attr.webViewStyle);
142     }
143
144     // The full constructor.
145     public NestedScrollWebView(Context context, AttributeSet attributeSet, int defaultStyle) {
146         // Run the default commands.
147         super(context, attributeSet, defaultStyle);
148
149         // Initialize the nested scrolling child helper.
150         nestedScrollingChildHelper = new NestedScrollingChildHelper(this);
151
152         // Enable nested scrolling by default.
153         nestedScrollingChildHelper.setNestedScrollingEnabled(true);
154     }
155
156
157
158     // WebView Fragment ID.
159     public void setWebViewFragmentId(long webViewFragmentId) {
160         // Store the WebView fragment ID.
161         this.webViewFragmentId = webViewFragmentId;
162     }
163
164     public long getWebViewFragmentId() {
165         // Return the WebView fragment ID.
166         return webViewFragmentId;
167     }
168
169
170     // SSL error handler.
171     public void setSslErrorHandler(SslErrorHandler sslErrorHandler) {
172         // Store the current SSL error handler.
173         this.sslErrorHandler = sslErrorHandler;
174     }
175
176     public SslErrorHandler getSslErrorHandler() {
177         // Return the current SSL error handler.
178         return sslErrorHandler;
179     }
180
181     public void resetSslErrorHandler() {
182         // Reset the current SSL error handler.
183         sslErrorHandler = null;
184     }
185
186
187     // HTTP authentication handler.
188     public void setHttpAuthHandler(HttpAuthHandler httpAuthHandler) {
189         // Store the current HTTP authentication handler.
190         this.httpAuthHandler = httpAuthHandler;
191     }
192
193     public HttpAuthHandler getHttpAuthHandler() {
194         // Return the current HTTP authentication handler.
195         return httpAuthHandler;
196     }
197
198     public void resetHttpAuthHandler() {
199         // Reset the current HTTP authentication handler.
200         httpAuthHandler = null;
201     }
202
203
204     // Domain settings.
205     public void setDomainSettingsApplied(boolean applied) {
206         // Store the domain settings applied status.
207         domainSettingsApplied = applied;
208     }
209
210     public boolean getDomainSettingsApplied() {
211         // Return the domain settings applied status.
212         return domainSettingsApplied;
213     }
214
215
216     // Domain settings database ID.
217     public void setDomainSettingsDatabaseId(int databaseId) {
218         // Store the domain settings database ID.
219         domainSettingsDatabaseId = databaseId;
220     }
221
222     public int getDomainSettingsDatabaseId() {
223         // Return the domain settings database ID.
224         return domainSettingsDatabaseId;
225     }
226
227
228     // Current domain name.  To function well when called, the domain name should never be allowed to be null.
229     public void setCurrentDomainName(@NonNull String domainName) {
230         // Store the current domain name.
231         currentDomainName = domainName;
232     }
233
234     public void resetCurrentDomainName() {
235         // Reset the current domain name.
236         currentDomainName = "";
237     }
238
239     public String getCurrentDomainName() {
240         // Return the current domain name.
241         return currentDomainName;
242     }
243
244
245     // First-party cookies.
246     public void setAcceptFirstPartyCookies(boolean status) {
247         // Store the accept first-party cookies status.
248         acceptFirstPartyCookies = status;
249     }
250
251     public boolean getAcceptFirstPartyCookies() {
252         // Return the accept first-party cookies status.
253         return acceptFirstPartyCookies;
254     }
255
256
257     // Domain settings JavaScript enabled.  This can be removed once night mode does not require JavaScript.
258     public void setDomainSettingsJavaScriptEnabled(boolean status) {
259         // Store the domain settings JavaScript status.
260         domainSettingsJavaScriptEnabled = status;
261     }
262
263     public boolean getDomainSettingsJavaScriptEnabled() {
264         // Return the domain settings JavaScript status.
265         return domainSettingsJavaScriptEnabled;
266     }
267
268
269     // Resource requests.
270     public void addResourceRequest(String[] resourceRequest) {
271         // Add the resource request to the list.
272         resourceRequests.add(resourceRequest);
273     }
274
275     public ArrayList<String[]> getResourceRequests() {
276         // Return the list of resource requests.
277         return resourceRequests;
278     }
279
280     public void clearResourceRequests() {
281         // Clear the resource requests.
282         resourceRequests.clear();
283     }
284
285
286     // Blocklists.
287     public void enableBlocklist(int blocklist, boolean status) {
288         // Update the status of the indicated blocklist.
289         switch (blocklist) {
290             case EASY_LIST:
291                 // Update the status of the blocklist.
292                 easyListEnabled = status;
293                 break;
294
295             case EASY_PRIVACY:
296                 // Update the status of the blocklist.
297                 easyPrivacyEnabled = status;
298                 break;
299
300             case FANBOYS_ANNOYANCE_LIST:
301                 // Update the status of the blocklist.
302                 fanboysAnnoyanceListEnabled = status;
303                 break;
304
305             case FANBOYS_SOCIAL_BLOCKING_LIST:
306                 // Update the status of the blocklist.
307                 fanboysSocialBlockingListEnabled = status;
308                 break;
309
310             case ULTRA_PRIVACY:
311                 // Update the status of the blocklist.
312                 ultraPrivacyEnabled = status;
313                 break;
314
315             case THIRD_PARTY_REQUESTS:
316                 // Update the status of the blocklist.
317                 blockAllThirdPartyRequests = status;
318                 break;
319         }
320     }
321
322     public boolean isBlocklistEnabled(int blocklist) {
323         // Get the status of the indicated blocklist.
324         switch (blocklist) {
325             case EASY_LIST:
326                 // Return the status of the blocklist.
327                 return easyListEnabled;
328
329             case EASY_PRIVACY:
330                 // Return the status of the blocklist.
331                 return easyPrivacyEnabled;
332
333             case FANBOYS_ANNOYANCE_LIST:
334                 // Return the status of the blocklist.
335                 return fanboysAnnoyanceListEnabled;
336
337             case FANBOYS_SOCIAL_BLOCKING_LIST:
338                 // Return the status of the blocklist.
339                 return fanboysSocialBlockingListEnabled;
340
341             case ULTRA_PRIVACY:
342                 // Return the status of the blocklist.
343                 return ultraPrivacyEnabled;
344
345             case THIRD_PARTY_REQUESTS:
346                 // Return the status of the blocklist.
347                 return blockAllThirdPartyRequests;
348
349             default:
350                 // The default value is required but should never be used.
351                 return false;
352         }
353     }
354
355
356     // Resource request counters.
357     public void resetRequestsCounters() {
358         // Reset all the resource request counters.
359         blockedRequests = 0;
360         easyListBlockedRequests = 0;
361         easyPrivacyBlockedRequests = 0;
362         fanboysAnnoyanceListBlockedRequests = 0;
363         fanboysSocialBlockingListBlockedRequests = 0;
364         ultraPrivacyBlockedRequests = 0;
365         thirdPartyBlockedRequests = 0;
366     }
367
368     public void incrementRequestsCount(int blocklist) {
369         // Increment the count of the indicated blocklist.
370         switch (blocklist) {
371             case BLOCKED_REQUESTS:
372                 // Increment the blocked requests count.
373                 blockedRequests++;
374                 break;
375
376             case EASY_LIST:
377                 // Increment the EasyList blocked requests count.
378                 easyListBlockedRequests++;
379                 break;
380
381             case EASY_PRIVACY:
382                 // Increment the EasyPrivacy blocked requests count.
383                 easyPrivacyBlockedRequests++;
384                 break;
385
386             case FANBOYS_ANNOYANCE_LIST:
387                 // Increment the Fanboy's Annoyance List blocked requests count.
388                 fanboysAnnoyanceListBlockedRequests++;
389                 break;
390
391             case FANBOYS_SOCIAL_BLOCKING_LIST:
392                 // Increment the Fanboy's Social Blocking List blocked requests count.
393                 fanboysSocialBlockingListBlockedRequests++;
394                 break;
395
396             case ULTRA_PRIVACY:
397                 // Increment the UltraPrivacy blocked requests count.
398                 ultraPrivacyBlockedRequests++;
399                 break;
400
401             case THIRD_PARTY_REQUESTS:
402                 // Increment the Third Party blocked requests count.
403                 thirdPartyBlockedRequests++;
404                 break;
405         }
406     }
407
408     public int getRequestsCount(int blocklist) {
409         // Get the count of the indicated blocklist.
410         switch (blocklist) {
411             case BLOCKED_REQUESTS:
412                 // Return the blocked requests count.
413                 return blockedRequests;
414
415             case EASY_LIST:
416                 // Return the EasyList blocked requests count.
417                 return easyListBlockedRequests;
418
419             case EASY_PRIVACY:
420                 // Return the EasyPrivacy blocked requests count.
421                 return easyPrivacyBlockedRequests;
422
423             case FANBOYS_ANNOYANCE_LIST:
424                 // Return the Fanboy's Annoyance List blocked requests count.
425                 return fanboysAnnoyanceListBlockedRequests;
426
427             case FANBOYS_SOCIAL_BLOCKING_LIST:
428                 // Return the Fanboy's Social Blocking List blocked requests count.
429                 return fanboysSocialBlockingListBlockedRequests;
430
431             case ULTRA_PRIVACY:
432                 // Return the UltraPrivacy blocked requests count.
433                 return ultraPrivacyBlockedRequests;
434
435             case THIRD_PARTY_REQUESTS:
436                 // Return the Third Party blocked requests count.
437                 return thirdPartyBlockedRequests;
438
439             default:
440                 // Return 0.  This should never end up being called.
441                 return 0;
442         }
443     }
444
445
446     // Pinned SSL certificates.
447     public boolean hasPinnedSslCertificate() {
448         // Return the status of the pinned SSL certificate.
449         return hasPinnedSslCertificate;
450     }
451
452     public void setPinnedSslCertificate(String issuedToCName, String issuedToOName, String issuedToUName, String issuedByCName, String issuedByOName, String issuedByUName, Date startDate, Date endDate) {
453         // Store the pinned SSL certificate information.
454         pinnedSslIssuedToCName = issuedToCName;
455         pinnedSslIssuedToOName = issuedToOName;
456         pinnedSslIssuedToUName = issuedToUName;
457         pinnedSslIssuedByCName = issuedByCName;
458         pinnedSslIssuedByOName = issuedByOName;
459         pinnedSslIssuedByUName = issuedByUName;
460         pinnedSslStartDate = startDate;
461         pinnedSslEndDate = endDate;
462
463         // Set the pinned SSL certificate tracker.
464         hasPinnedSslCertificate = true;
465     }
466
467     public ArrayList<Object> getPinnedSslCertificate() {
468         // Initialize an array list.
469         ArrayList<Object> arrayList = new ArrayList<>();
470
471         // Create the SSL certificate string array.
472         String[] sslCertificateStringArray = new String[] {pinnedSslIssuedToCName, pinnedSslIssuedToOName, pinnedSslIssuedToUName, pinnedSslIssuedByCName, pinnedSslIssuedByOName, pinnedSslIssuedByUName};
473
474         // Create the SSL certificate date array.
475         Date[] sslCertificateDateArray = new Date[] {pinnedSslStartDate, pinnedSslEndDate};
476
477         // Add the arrays to the array list.
478         arrayList.add(sslCertificateStringArray);
479         arrayList.add(sslCertificateDateArray);
480
481         // Return the pinned SSL certificate array list.
482         return arrayList;
483     }
484
485     public void clearPinnedSslCertificate() {
486         // Clear the pinned SSL certificate.
487         pinnedSslIssuedToCName = null;
488         pinnedSslIssuedToOName = null;
489         pinnedSslIssuedToUName = null;
490         pinnedSslIssuedByCName = null;
491         pinnedSslIssuedByOName = null;
492         pinnedSslIssuedByUName = null;
493         pinnedSslStartDate = null;
494         pinnedSslEndDate = null;
495
496         // Clear the pinned SSL certificate tracker.
497         hasPinnedSslCertificate = false;
498     }
499
500
501     // Current IP addresses.
502     public boolean hasCurrentIpAddresses() {
503         // Return the status of the current IP addresses.
504         return hasCurrentIpAddresses;
505     }
506
507     public void setCurrentIpAddresses(String ipAddresses) {
508         // Store the current IP addresses.
509         currentIpAddresses = ipAddresses;
510
511         // Set the current IP addresses tracker.
512         hasCurrentIpAddresses = true;
513     }
514
515     public String getCurrentIpAddresses() {
516         // Return the current IP addresses.
517         return currentIpAddresses;
518     }
519
520     public void clearCurrentIpAddresses() {
521         // Clear the current IP addresses.
522         currentIpAddresses = null;
523
524         // Clear the current IP addresses tracker.
525         hasCurrentIpAddresses = false;
526     }
527
528
529     // Pinned IP addresses.
530     public boolean hasPinnedIpAddresses() {
531         // Return the status of the pinned IP addresses.
532         return hasPinnedIpAddresses;
533     }
534
535     public void setPinnedIpAddresses(String ipAddresses) {
536         // Store the pinned IP addresses.
537         pinnedIpAddresses = ipAddresses;
538
539         // Set the pinned IP addresses tracker.
540         hasPinnedIpAddresses = true;
541     }
542
543     public String getPinnedIpAddresses() {
544         // Return the pinned IP addresses.
545         return pinnedIpAddresses;
546     }
547
548     public void clearPinnedIpAddresses() {
549         // Clear the pinned IP addresses.
550         pinnedIpAddresses = null;
551
552         // Clear the pinned IP addresses tracker.
553         hasPinnedIpAddresses = false;
554     }
555
556
557     // Ignore pinned information.
558     public void setIgnorePinnedDomainInformation(boolean status) {
559         // Set the status of the ignore pinned domain information tracker.
560         ignorePinnedDomainInformation = status;
561     }
562
563     // The syntax looks better as written, even if it is always inverted.
564     @SuppressWarnings("BooleanMethodIsAlwaysInverted")
565     public boolean ignorePinnedDomainInformation() {
566         // Return the status of the ignore pinned domain information tracker.
567         return ignorePinnedDomainInformation;
568     }
569
570
571     // Navigating history.
572     public void setNavigatingHistory(boolean status) {
573         // Set the status of navigating history.
574         navigatingHistory = status;
575     }
576
577     public boolean getNavigatingHistory() {
578         // Return the status of navigating history.
579         return navigatingHistory;
580     }
581
582
583     // Favorite or default icon.
584     public void initializeFavoriteIcon() {
585         // Get the default favorite icon drawable.  `ContextCompat` must be used until API >= 21.
586         Drawable favoriteIconDrawable = ContextCompat.getDrawable(getContext(), R.drawable.world);
587
588         // Cast the favorite icon drawable to a bitmap drawable.
589         BitmapDrawable favoriteIconBitmapDrawable = (BitmapDrawable) favoriteIconDrawable;
590
591         // Remove the incorrect warning below that the favorite icon bitmap drawable might be null.
592         assert favoriteIconBitmapDrawable != null;
593
594         // Store the default icon bitmap.
595         favoriteOrDefaultIcon = favoriteIconBitmapDrawable.getBitmap();
596     }
597
598     public void setFavoriteOrDefaultIcon(Bitmap icon) {
599         // Scale the favorite icon bitmap down if it is larger than 256 x 256.  Filtering uses bilinear interpolation.
600         if ((icon.getHeight() > 256) || (icon.getWidth() > 256)) {
601             favoriteOrDefaultIcon = Bitmap.createScaledBitmap(icon, 256, 256, true);
602         } else {
603             // Store the icon as presented.
604             favoriteOrDefaultIcon = icon;
605         }
606     }
607
608     public Bitmap getFavoriteOrDefaultIcon() {
609         // Return the favorite or default icon.
610         return favoriteOrDefaultIcon;
611     }
612
613
614     // Night mode.
615     public void setNightMode(boolean status) {
616         // Store the night mode status.
617         nightMode = status;
618     }
619
620     public boolean getNightMode() {
621         // Return the night mode status.
622         return nightMode;
623     }
624
625
626     // Swipe to refresh.
627     public void setSwipeToRefresh(boolean status) {
628         // Store the swipe to refresh status.
629         swipeToRefresh = status;
630     }
631
632     public boolean getSwipeToRefresh() {
633         // Return the swipe to refresh status.
634         return swipeToRefresh;
635     }
636
637
638     // Scroll range.
639     public int getHorizontalScrollRange() {
640         // Return the horizontal scroll range.
641         return computeHorizontalScrollRange();
642     }
643
644     public int getVerticalScrollRange() {
645         // Return the vertical scroll range.
646         return computeVerticalScrollRange();
647     }
648
649
650
651     @Override
652     public boolean onTouchEvent(MotionEvent motionEvent) {
653         // Initialize a tracker to return if this motion event is handled.
654         boolean motionEventHandled;
655
656         // Run the commands for the given motion event action.
657         switch (motionEvent.getAction()) {
658             case MotionEvent.ACTION_DOWN:
659                 // Start nested scrolling along the vertical axis.  `ViewCompat` must be used until the minimum API >= 21.
660                 startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
661
662                 // Save the current Y position.  Action down will not be called again until a new motion starts.
663                 previousYPosition = (int) motionEvent.getY();
664
665                 // Run the default commands.
666                 motionEventHandled = super.onTouchEvent(motionEvent);
667                 break;
668
669             case MotionEvent.ACTION_MOVE:
670                 // Get the current Y position.
671                 int currentYMotionPosition = (int) motionEvent.getY();
672
673                 // Calculate the pre-scroll delta Y.
674                 int preScrollDeltaY = previousYPosition - currentYMotionPosition;
675
676                 // Initialize a variable to track how much of the scroll is consumed.
677                 int[] consumedScroll = new int[2];
678
679                 // Initialize a variable to track the offset in the window.
680                 int[] offsetInWindow = new int[2];
681
682                 // Get the WebView Y position.
683                 int webViewYPosition = getScrollY();
684
685                 // Set the scroll delta Y to initially be the same as the pre-scroll delta Y.
686                 int scrollDeltaY = preScrollDeltaY;
687
688                 // Dispatch the nested pre-school.  This scrolls the app bar if it needs it.  `offsetInWindow` will be returned with an updated value.
689                 if (dispatchNestedPreScroll(0, preScrollDeltaY, consumedScroll, offsetInWindow)) {
690                     // Update the scroll delta Y if some of it was consumed.
691                     // There is currently a bug in Android where if scrolling up at a certain slow speed the input can lock the pre scroll and continue to consume it after the app bar is fully displayed.
692                     scrollDeltaY = preScrollDeltaY - consumedScroll[1];
693                 }
694
695                 // Check to see if the WebView is at the top and and the scroll action is downward.
696                 if ((webViewYPosition == 0) && (scrollDeltaY < 0)) {  // Swipe to refresh is being engaged.
697                     // 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.
698                     stopNestedScroll();
699                 } else {  // Swipe to refresh is not being engaged.
700                     // Start the nested scroll so that the app bar can scroll off the screen.
701                     startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
702
703                     // 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.
704                     dispatchNestedScroll(0, scrollDeltaY, 0, 0, offsetInWindow);
705
706                     // Store the current Y position for use in the next action move.
707                     previousYPosition = previousYPosition - scrollDeltaY;
708                 }
709
710                 // Run the default commands.
711                 motionEventHandled = super.onTouchEvent(motionEvent);
712                 break;
713
714
715             default:
716                 // Stop nested scrolling.
717                 stopNestedScroll();
718
719                 // Run the default commands.
720                 motionEventHandled = super.onTouchEvent(motionEvent);
721         }
722
723         // Perform a click.  This is required by the Android accessibility guidelines.
724         performClick();
725
726         // Return the status of the motion event.
727         return motionEventHandled;
728     }
729
730     // The Android accessibility guidelines require overriding `performClick()` and calling it from `onTouchEvent()`.
731     @Override
732     public boolean performClick() {
733         return super.performClick();
734     }
735
736
737     // Method from NestedScrollingChild.
738     @Override
739     public void setNestedScrollingEnabled(boolean status) {
740         // Set the status of the nested scrolling.
741         nestedScrollingChildHelper.setNestedScrollingEnabled(status);
742     }
743
744     // Method from NestedScrollingChild.
745     @Override
746     public boolean isNestedScrollingEnabled() {
747         // Return the status of nested scrolling.
748         return nestedScrollingChildHelper.isNestedScrollingEnabled();
749     }
750
751
752     // Method from NestedScrollingChild.
753     @Override
754     public boolean startNestedScroll(int axes) {
755         // Start a nested scroll along the indicated axes.
756         return nestedScrollingChildHelper.startNestedScroll(axes);
757     }
758
759     // Method from NestedScrollingChild2.
760     @Override
761     public boolean startNestedScroll(int axes, int type) {
762         // Start a nested scroll along the indicated axes for the given type of input which caused the scroll event.
763         return nestedScrollingChildHelper.startNestedScroll(axes, type);
764     }
765
766
767     // Method from NestedScrollingChild.
768     @Override
769     public void stopNestedScroll() {
770         // Stop the nested scroll.
771         nestedScrollingChildHelper.stopNestedScroll();
772     }
773
774     // Method from NestedScrollingChild2.
775     @Override
776     public void stopNestedScroll(int type) {
777         // Stop the nested scroll of the given type of input which caused the scroll event.
778         nestedScrollingChildHelper.stopNestedScroll(type);
779     }
780
781
782     // Method from NestedScrollingChild.
783     @Override
784     public boolean hasNestedScrollingParent() {
785         // Return the status of the nested scrolling parent.
786         return nestedScrollingChildHelper.hasNestedScrollingParent();
787     }
788
789     // Method from NestedScrollingChild2.
790     @Override
791     public boolean hasNestedScrollingParent(int type) {
792         // return the status of the nested scrolling parent for the given type of input which caused the scroll event.
793         return nestedScrollingChildHelper.hasNestedScrollingParent(type);
794     }
795
796
797     // Method from NestedScrollingChild.
798     @Override
799     public boolean dispatchNestedPreScroll(int deltaX, int deltaY, int[] consumed, int[] offsetInWindow) {
800         // Dispatch a nested pre-scroll with the specified deltas, which lets a parent to consume some of the scroll if desired.
801         return nestedScrollingChildHelper.dispatchNestedPreScroll(deltaX, deltaY, consumed, offsetInWindow);
802     }
803
804     // Method from NestedScrollingChild2.
805     @Override
806     public boolean dispatchNestedPreScroll(int deltaX, int deltaY, int[] consumed, int[] offsetInWindow, int type) {
807         // 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.
808         return nestedScrollingChildHelper.dispatchNestedPreScroll(deltaX, deltaY, consumed, offsetInWindow, type);
809     }
810
811
812     // Method from NestedScrollingChild.
813     @Override
814     public boolean dispatchNestedScroll(int deltaXConsumed, int deltaYConsumed, int deltaXUnconsumed, int deltaYUnconsumed, int[] offsetInWindow) {
815         // Dispatch a nested scroll with the specified deltas.
816         return nestedScrollingChildHelper.dispatchNestedScroll(deltaXConsumed, deltaYConsumed, deltaXUnconsumed, deltaYUnconsumed, offsetInWindow);
817     }
818
819     // Method from NestedScrollingChild2.
820     @Override
821     public boolean dispatchNestedScroll(int deltaXConsumed, int deltaYConsumed, int deltaXUnconsumed, int deltaYUnconsumed, int[] offsetInWindow, int type) {
822         // Dispatch a nested scroll with the specified deltas for the given type of input which caused the scroll event.
823         return nestedScrollingChildHelper.dispatchNestedScroll(deltaXConsumed, deltaYConsumed, deltaXUnconsumed, deltaYUnconsumed, offsetInWindow, type);
824     }
825
826
827     // Method from NestedScrollingChild.
828     @Override
829     public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
830         // Dispatch a nested pre-fling with the specified velocity, which lets a parent consume the fling if desired.
831         return nestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);
832     }
833
834     // Method from NestedScrollingChild.
835     @Override
836     public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
837         // Dispatch a nested fling with the specified velocity.
838         return nestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
839     }
840 }