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