]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/views/NestedScrollWebView.java
Make pinned settings tab aware.
[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.util.AttributeSet;
24 import android.view.MotionEvent;
25 import android.webkit.WebView;
26
27 import androidx.core.view.NestedScrollingChild2;
28 import androidx.core.view.NestedScrollingChildHelper;
29 import androidx.core.view.ViewCompat;
30
31 import java.util.ArrayList;
32 import java.util.Date;
33
34 // NestedScrollWebView extends WebView to handle nested scrolls (scrolling the app bar off the screen).
35 public class NestedScrollWebView extends WebView implements NestedScrollingChild2 {
36     // These constants identify the blocklists.
37     public final static int BLOCKED_REQUESTS = 0;
38     public final static int EASY_LIST_BLOCKED_REQUESTS = 1;
39     public final static int EASY_PRIVACY_BLOCKED_REQUESTS = 2;
40     public final static int FANBOYS_ANNOYANCE_LIST_BLOCKED_REQUESTS = 3;
41     public final static int FANBOYS_SOCIAL_BLOCKING_LIST_BLOCKED_REQUESTS = 4;
42     public final static int ULTRA_PRIVACY_BLOCKED_REQUESTS = 5;
43     public final static int THIRD_PARTY_BLOCKED_REQUESTS = 6;
44
45     // Keep a copy of the WebView fragment ID.
46     private long webViewFragmentId;
47
48     // Track if domain settings are applied to this nested scroll WebView and, if so, the database ID.
49     private boolean domainSettingsApplied;
50     private int domainSettingsDatabaseId;
51
52     // Track the resource requests.
53     private ArrayList<String[]> resourceRequests = new ArrayList<>();
54     private int blockedRequests;
55     private int easyListBlockedRequests;
56     private int easyPrivacyBlockedRequests;
57     private int fanboysAnnoyanceListBlockedRequests;
58     private int fanboysSocialBlockingListBlockedRequests;
59     private int ultraPrivacyBlockedRequests;
60     private int thirdPartyBlockedRequests;
61
62     // The pinned SSL certificate variables.
63     private boolean hasPinnedSslCertificate;
64     private String pinnedSslIssuedToCName;
65     private String pinnedSslIssuedToOName;
66     private String pinnedSslIssuedToUName;
67     private String pinnedSslIssuedByCName;
68     private String pinnedSslIssuedByOName;
69     private String pinnedSslIssuedByUName;
70     private Date pinnedSslStartDate;
71     private Date pinnedSslEndDate;
72
73     // The current IP addresses variables.
74     private boolean hasCurrentIpAddresses;
75     private String currentIpAddresses;
76
77     // The pinned IP addresses variables.
78     private boolean hasPinnedIpAddresses;
79     private String pinnedIpAddresses;
80
81     // 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.
82     private boolean ignorePinnedDomainInformation;
83
84     // The nested scrolling child helper is used throughout the class.
85     private NestedScrollingChildHelper nestedScrollingChildHelper;
86
87     // The previous Y position needs to be tracked between motion events.
88     private int previousYPosition;
89
90
91
92     // The basic constructor.
93     public NestedScrollWebView(Context context) {
94         // Roll up to the next constructor.
95         this(context, null);
96     }
97
98     // The intermediate constructor.
99     public NestedScrollWebView(Context context, AttributeSet attributeSet) {
100         // Roll up to the next constructor.
101         this(context, attributeSet, android.R.attr.webViewStyle);
102     }
103
104     // The full constructor.
105     public NestedScrollWebView(Context context, AttributeSet attributeSet, int defaultStyle) {
106         // Run the default commands.
107         super(context, attributeSet, defaultStyle);
108
109         // Initialize the nested scrolling child helper.
110         nestedScrollingChildHelper = new NestedScrollingChildHelper(this);
111
112         // Enable nested scrolling by default.
113         nestedScrollingChildHelper.setNestedScrollingEnabled(true);
114     }
115
116
117
118     // WebView Fragment ID.
119     public void setWebViewFragmentId(long webViewFragmentId) {
120         // Store the WebView fragment ID.
121         this.webViewFragmentId = webViewFragmentId;
122     }
123
124     public long getWebViewFragmentId() {
125         // Return the WebView fragment ID.
126         return webViewFragmentId;
127     }
128
129
130     // Domain settings.
131     public void setDomainSettingsApplied(boolean applied) {
132         // Store the domain settings applied status.
133         domainSettingsApplied = applied;
134     }
135
136     public boolean getDomainSettingsApplied() {
137         // Return the domain settings applied status.
138         return domainSettingsApplied;
139     }
140
141
142     // Domain settings database ID.
143     public void setDomainSettingsDatabaseId(int databaseId) {
144         // Store the domain settings database ID.
145         domainSettingsDatabaseId = databaseId;
146     }
147
148     public int getDomainSettingsDatabaseId() {
149         // Return the domain settings database ID.
150         return domainSettingsDatabaseId;
151     }
152
153
154     // Resource requests.
155     public void addResourceRequest(String[] resourceRequest) {
156         // Add the resource request to the list.
157         resourceRequests.add(resourceRequest);
158     }
159
160     public ArrayList<String[]> getResourceRequests() {
161         // Return the list of resource requests.
162         return resourceRequests;
163     }
164
165     public void clearResourceRequests() {
166         // Clear the resource requests.
167         resourceRequests.clear();
168     }
169
170
171     // Resource request counters.
172     public void resetRequestsCount(int list) {
173         // Run the command on the indicated list.
174         switch (list) {
175             case BLOCKED_REQUESTS:
176                 // Reset the blocked requests count.
177                 blockedRequests = 0;
178                 break;
179
180             case EASY_LIST_BLOCKED_REQUESTS:
181                 // Reset the EasyList blocked requests count.
182                 easyListBlockedRequests = 0;
183                 break;
184
185             case EASY_PRIVACY_BLOCKED_REQUESTS:
186                 // Reset the EasyPrivacy blocked requests count.
187                 easyPrivacyBlockedRequests = 0;
188                 break;
189
190             case FANBOYS_ANNOYANCE_LIST_BLOCKED_REQUESTS:
191                 // Reset the Fanboy's Annoyance List blocked requests count.
192                 fanboysAnnoyanceListBlockedRequests = 0;
193                 break;
194
195             case FANBOYS_SOCIAL_BLOCKING_LIST_BLOCKED_REQUESTS:
196                 // Reset the Fanboy's Social Blocking List blocked requests count.
197                 fanboysSocialBlockingListBlockedRequests = 0;
198                 break;
199
200             case ULTRA_PRIVACY_BLOCKED_REQUESTS:
201                 // Reset the UltraPrivacy blocked requests count.
202                 ultraPrivacyBlockedRequests = 0;
203                 break;
204
205             case THIRD_PARTY_BLOCKED_REQUESTS:
206                 // Reset the Third Party blocked requests count.
207                 thirdPartyBlockedRequests = 0;
208                 break;
209         }
210     }
211
212     public void incrementRequestsCount(int list) {
213         // Run the command on the indicated list.
214         switch (list) {
215             case BLOCKED_REQUESTS:
216                 // Increment the blocked requests count.
217                 blockedRequests++;
218                 break;
219
220             case EASY_LIST_BLOCKED_REQUESTS:
221                 // Increment the EasyList blocked requests count.
222                 easyListBlockedRequests++;
223                 break;
224
225             case EASY_PRIVACY_BLOCKED_REQUESTS:
226                 // Increment the EasyPrivacy blocked requests count.
227                 easyPrivacyBlockedRequests++;
228                 break;
229
230             case FANBOYS_ANNOYANCE_LIST_BLOCKED_REQUESTS:
231                 // Increment the Fanboy's Annoyance List blocked requests count.
232                 fanboysAnnoyanceListBlockedRequests++;
233                 break;
234
235             case FANBOYS_SOCIAL_BLOCKING_LIST_BLOCKED_REQUESTS:
236                 // Increment the Fanboy's Social Blocking List blocked requests count.
237                 fanboysSocialBlockingListBlockedRequests++;
238                 break;
239
240             case ULTRA_PRIVACY_BLOCKED_REQUESTS:
241                 // Increment the UltraPrivacy blocked requests count.
242                 ultraPrivacyBlockedRequests++;
243                 break;
244
245             case THIRD_PARTY_BLOCKED_REQUESTS:
246                 // Increment the Third Party blocked requests count.
247                 thirdPartyBlockedRequests++;
248                 break;
249         }
250     }
251
252     public int getRequestsCount(int list) {
253         // Run the command on the indicated list.
254         switch (list) {
255             case BLOCKED_REQUESTS:
256                 // Return the blocked requests count.
257                 return blockedRequests;
258
259             case EASY_LIST_BLOCKED_REQUESTS:
260                 // Return the EasyList blocked requests count.
261                 return easyListBlockedRequests;
262
263             case EASY_PRIVACY_BLOCKED_REQUESTS:
264                 // Return the EasyPrivacy blocked requests count.
265                 return easyPrivacyBlockedRequests;
266
267             case FANBOYS_ANNOYANCE_LIST_BLOCKED_REQUESTS:
268                 // Return the Fanboy's Annoyance List blocked requests count.
269                 return fanboysAnnoyanceListBlockedRequests;
270
271             case FANBOYS_SOCIAL_BLOCKING_LIST_BLOCKED_REQUESTS:
272                 // Return the Fanboy's Social Blocking List blocked requests count.
273                 return fanboysSocialBlockingListBlockedRequests;
274
275             case ULTRA_PRIVACY_BLOCKED_REQUESTS:
276                 // Return the UltraPrivacy blocked requests count.
277                 return ultraPrivacyBlockedRequests;
278
279             case THIRD_PARTY_BLOCKED_REQUESTS:
280                 // Return the Third Party blocked requests count.
281                 return thirdPartyBlockedRequests;
282
283             default:
284                 // Return 0.  This should never end up being called.
285                 return 0;
286         }
287     }
288
289
290     // Pinned SSL certificates.
291     public boolean hasPinnedSslCertificate() {
292         // Return the status of the pinned SSL certificate.
293         return hasPinnedSslCertificate;
294     }
295
296     public void setPinnedSslCertificate(String issuedToCName, String issuedToOName, String issuedToUName, String issuedByCName, String issuedByOName, String issuedByUName, Date startDate, Date endDate) {
297         // Store the pinned SSL certificate information.
298         pinnedSslIssuedToCName = issuedToCName;
299         pinnedSslIssuedToOName = issuedToOName;
300         pinnedSslIssuedToUName = issuedToUName;
301         pinnedSslIssuedByCName = issuedByCName;
302         pinnedSslIssuedByOName = issuedByOName;
303         pinnedSslIssuedByUName = issuedByUName;
304         pinnedSslStartDate = startDate;
305         pinnedSslEndDate = endDate;
306
307         // Set the pinned SSL certificate tracker.
308         hasPinnedSslCertificate = true;
309     }
310
311     public ArrayList<Object> getPinnedSslCertificate() {
312         // Initialize an array list.
313         ArrayList<Object> arrayList = new ArrayList<>();
314
315         // Create the SSL certificate string array.
316         String[] sslCertificateStringArray = new String[] {pinnedSslIssuedToCName, pinnedSslIssuedToOName, pinnedSslIssuedToUName, pinnedSslIssuedByCName, pinnedSslIssuedByOName, pinnedSslIssuedByUName};
317
318         // Create the SSL certificate date array.
319         Date[] sslCertificateDateArray = new Date[] {pinnedSslStartDate, pinnedSslEndDate};
320
321         // Add the arrays to the array list.
322         arrayList.add(sslCertificateStringArray);
323         arrayList.add(sslCertificateDateArray);
324
325         // Return the pinned SSL certificate array list.
326         return arrayList;
327     }
328
329     public void clearPinnedSslCertificate() {
330         // Clear the pinned SSL certificate.
331         pinnedSslIssuedToCName = null;
332         pinnedSslIssuedToOName = null;
333         pinnedSslIssuedToUName = null;
334         pinnedSslIssuedByCName = null;
335         pinnedSslIssuedByOName = null;
336         pinnedSslIssuedByUName = null;
337         pinnedSslStartDate = null;
338         pinnedSslEndDate = null;
339
340         // Clear the pinned SSL certificate tracker.
341         hasPinnedSslCertificate = false;
342     }
343
344
345     // Current IP addresses.
346     public boolean hasCurrentIpAddresses() {
347         // Return the status of the current IP addresses.
348         return hasCurrentIpAddresses;
349     }
350
351     public void setCurrentIpAddresses(String ipAddresses) {
352         // Store the current IP addresses.
353         currentIpAddresses = ipAddresses;
354
355         // Set the current IP addresses tracker.
356         hasCurrentIpAddresses = true;
357     }
358
359     public String getCurrentIpAddresses() {
360         // Return the current IP addresses.
361         return currentIpAddresses;
362     }
363
364     public void clearCurrentIpAddresses() {
365         // Clear the current IP addresses.
366         currentIpAddresses = null;
367
368         // Clear the current IP addresses tracker.
369         hasCurrentIpAddresses = false;
370     }
371
372
373     // Pinned IP addresses.
374     public boolean hasPinnedIpAddresses() {
375         // Return the status of the pinned IP addresses.
376         return hasPinnedIpAddresses;
377     }
378
379     public void setPinnedIpAddresses(String ipAddresses) {
380         // Store the pinned IP addresses.
381         pinnedIpAddresses = ipAddresses;
382
383         // Set the pinned IP addresses tracker.
384         hasPinnedIpAddresses = true;
385     }
386
387     public String getPinnedIpAddresses() {
388         // Return the pinned IP addresses.
389         return pinnedIpAddresses;
390     }
391
392     public void clearPinnedIpAddresses() {
393         // Clear the pinned IP addresses.
394         pinnedIpAddresses = null;
395
396         // Clear the pinned IP addresses tracker.
397         hasPinnedIpAddresses = false;
398     }
399
400
401     // Ignore pinned information.  The syntax looks better as written, even if it is always inverted.
402     @SuppressWarnings("BooleanMethodIsAlwaysInverted")
403     public boolean ignorePinnedDomainInformation() {
404         // Return the status of the ignore pinned domain information tracker.
405         return ignorePinnedDomainInformation;
406     }
407
408     public void setIgnorePinnedDomainInformation(boolean status) {
409         // Set the status of the ignore pinned domain information tracker.
410         ignorePinnedDomainInformation = status;
411     }
412
413
414
415     @Override
416     public boolean onTouchEvent(MotionEvent motionEvent) {
417         // Initialize a tracker to return if this motion event is handled.
418         boolean motionEventHandled;
419
420         // Run the commands for the given motion event action.
421         switch (motionEvent.getAction()) {
422             case MotionEvent.ACTION_DOWN:
423                 // Start nested scrolling along the vertical axis.  `ViewCompat` must be used until the minimum API >= 21.
424                 startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
425
426                 // Save the current Y position.  Action down will not be called again until a new motion starts.
427                 previousYPosition = (int) motionEvent.getY();
428
429                 // Run the default commands.
430                 motionEventHandled = super.onTouchEvent(motionEvent);
431                 break;
432
433             case MotionEvent.ACTION_MOVE:
434                 // Get the current Y position.
435                 int currentYMotionPosition = (int) motionEvent.getY();
436
437                 // Calculate the pre-scroll delta Y.
438                 int preScrollDeltaY = previousYPosition - currentYMotionPosition;
439
440                 // Initialize a variable to track how much of the scroll is consumed.
441                 int[] consumedScroll = new int[2];
442
443                 // Initialize a variable to track the offset in the window.
444                 int[] offsetInWindow = new int[2];
445
446                 // Get the WebView Y position.
447                 int webViewYPosition = getScrollY();
448
449                 // Set the scroll delta Y to initially be the same as the pre-scroll delta Y.
450                 int scrollDeltaY = preScrollDeltaY;
451
452                 // Dispatch the nested pre-school.  This scrolls the app bar if it needs it.  `offsetInWindow` will be returned with an updated value.
453                 if (dispatchNestedPreScroll(0, preScrollDeltaY, consumedScroll, offsetInWindow)) {
454                     // Update the scroll delta Y if some of it was consumed.
455                     scrollDeltaY = preScrollDeltaY - consumedScroll[1];
456                 }
457
458                 // Check to see if the WebView is at the top and and the scroll action is downward.
459                 if ((webViewYPosition == 0) && (scrollDeltaY < 0)) {  // Swipe to refresh is being engaged.
460                     // Stop the nested scroll so that swipe to refresh has complete control.
461                     stopNestedScroll();
462                 } else {  // Swipe to refresh is not being engaged.
463                     // Start the nested scroll so that the app bar can scroll off the screen.
464                     startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
465
466                     // 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.
467                     dispatchNestedScroll(0, scrollDeltaY, 0, 0, offsetInWindow);
468
469                     // Store the current Y position for use in the next action move.
470                     previousYPosition = previousYPosition - scrollDeltaY;
471                 }
472
473                 // Run the default commands.
474                 motionEventHandled = super.onTouchEvent(motionEvent);
475                 break;
476
477
478             default:
479                 // Stop nested scrolling.
480                 stopNestedScroll();
481
482                 // Run the default commands.
483                 motionEventHandled = super.onTouchEvent(motionEvent);
484         }
485
486         // Perform a click.  This is required by the Android accessibility guidelines.
487         performClick();
488
489         // Return the status of the motion event.
490         return motionEventHandled;
491     }
492
493     // The Android accessibility guidelines require overriding `performClick()` and calling it from `onTouchEvent()`.
494     @Override
495     public boolean performClick() {
496         return super.performClick();
497     }
498
499
500     // Method from NestedScrollingChild.
501     @Override
502     public void setNestedScrollingEnabled(boolean status) {
503         // Set the status of the nested scrolling.
504         nestedScrollingChildHelper.setNestedScrollingEnabled(status);
505     }
506
507     // Method from NestedScrollingChild.
508     @Override
509     public boolean isNestedScrollingEnabled() {
510         // Return the status of nested scrolling.
511         return nestedScrollingChildHelper.isNestedScrollingEnabled();
512     }
513
514
515     // Method from NestedScrollingChild.
516     @Override
517     public boolean startNestedScroll(int axes) {
518         // Start a nested scroll along the indicated axes.
519         return nestedScrollingChildHelper.startNestedScroll(axes);
520     }
521
522     // Method from NestedScrollingChild2.
523     @Override
524     public boolean startNestedScroll(int axes, int type) {
525         // Start a nested scroll along the indicated axes for the given type of input which caused the scroll event.
526         return nestedScrollingChildHelper.startNestedScroll(axes, type);
527     }
528
529
530     // Method from NestedScrollingChild.
531     @Override
532     public void stopNestedScroll() {
533         // Stop the nested scroll.
534         nestedScrollingChildHelper.stopNestedScroll();
535     }
536
537     // Method from NestedScrollingChild2.
538     @Override
539     public void stopNestedScroll(int type) {
540         // Stop the nested scroll of the given type of input which caused the scroll event.
541         nestedScrollingChildHelper.stopNestedScroll(type);
542     }
543
544
545     // Method from NestedScrollingChild.
546     @Override
547     public boolean hasNestedScrollingParent() {
548         // Return the status of the nested scrolling parent.
549         return nestedScrollingChildHelper.hasNestedScrollingParent();
550     }
551
552     // Method from NestedScrollingChild2.
553     @Override
554     public boolean hasNestedScrollingParent(int type) {
555         // return the status of the nested scrolling parent for the given type of input which caused the scroll event.
556         return nestedScrollingChildHelper.hasNestedScrollingParent(type);
557     }
558
559
560     // Method from NestedScrollingChild.
561     @Override
562     public boolean dispatchNestedPreScroll(int deltaX, int deltaY, int[] consumed, int[] offsetInWindow) {
563         // Dispatch a nested pre-scroll with the specified deltas, which lets a parent to consume some of the scroll if desired.
564         return nestedScrollingChildHelper.dispatchNestedPreScroll(deltaX, deltaY, consumed, offsetInWindow);
565     }
566
567     // Method from NestedScrollingChild2.
568     @Override
569     public boolean dispatchNestedPreScroll(int deltaX, int deltaY, int[] consumed, int[] offsetInWindow, int type) {
570         // 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.
571         return nestedScrollingChildHelper.dispatchNestedPreScroll(deltaX, deltaY, consumed, offsetInWindow, type);
572     }
573
574
575     // Method from NestedScrollingChild.
576     @Override
577     public boolean dispatchNestedScroll(int deltaXConsumed, int deltaYConsumed, int deltaXUnconsumed, int deltaYUnconsumed, int[] offsetInWindow) {
578         // Dispatch a nested scroll with the specified deltas.
579         return nestedScrollingChildHelper.dispatchNestedScroll(deltaXConsumed, deltaYConsumed, deltaXUnconsumed, deltaYUnconsumed, offsetInWindow);
580     }
581
582     // Method from NestedScrollingChild2.
583     @Override
584     public boolean dispatchNestedScroll(int deltaXConsumed, int deltaYConsumed, int deltaXUnconsumed, int deltaYUnconsumed, int[] offsetInWindow, int type) {
585         // Dispatch a nested scroll with the specified deltas for the given type of input which caused the scroll event.
586         return nestedScrollingChildHelper.dispatchNestedScroll(deltaXConsumed, deltaYConsumed, deltaXUnconsumed, deltaYUnconsumed, offsetInWindow, type);
587     }
588
589
590     // Method from NestedScrollingChild.
591     @Override
592     public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
593         // Dispatch a nested pre-fling with the specified velocity, which lets a parent consume the fling if desired.
594         return nestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);
595     }
596
597     // Method from NestedScrollingChild.
598     @Override
599     public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
600         // Dispatch a nested fling with the specified velocity.
601         return nestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
602     }
603 }