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