2 * Copyright © 2019 Soren Stoutner <soren@stoutner.com>.
4 * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
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.
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.
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/>.
20 package com.stoutner.privacybrowser.views;
22 import android.content.Context;
23 import android.util.AttributeSet;
24 import android.view.MotionEvent;
25 import android.webkit.WebView;
27 import androidx.annotation.NonNull;
28 import androidx.core.view.NestedScrollingChild2;
29 import androidx.core.view.NestedScrollingChildHelper;
30 import androidx.core.view.ViewCompat;
32 import java.util.ArrayList;
33 import java.util.Date;
35 // NestedScrollWebView extends WebView to handle nested scrolls (scrolling the app bar off the screen).
36 public class NestedScrollWebView extends WebView implements NestedScrollingChild2 {
37 // These constants identify the blocklists.
38 public final static int BLOCKED_REQUESTS = 0;
39 public final static int EASY_LIST = 1;
40 public final static int EASY_PRIVACY = 2;
41 public final static int FANBOYS_ANNOYANCE_LIST = 3;
42 public final static int FANBOYS_SOCIAL_BLOCKING_LIST = 4;
43 public final static int ULTRA_PRIVACY = 5;
44 public final static int THIRD_PARTY_REQUESTS = 6;
46 // Keep a copy of the WebView fragment ID.
47 private long webViewFragmentId;
49 // Track if domain settings are applied to this nested scroll WebView and, if so, the database ID.
50 private boolean domainSettingsApplied;
51 private int domainSettingsDatabaseId;
53 // Keep track of when the domain name changes so that domain settings can be reapplied. This should never be null.
54 private String currentDomainName = "";
56 // Track the resource requests.
57 private ArrayList<String[]> resourceRequests = new ArrayList<>();
58 private boolean easyListEnabled;
59 private boolean easyPrivacyEnabled;
60 private boolean fanboysAnnoyanceListEnabled;
61 private boolean fanboysSocialBlockingListEnabled;
62 private boolean ultraPrivacyEnabled;
63 private boolean blockAllThirdPartyRequests;
64 private int blockedRequests;
65 private int easyListBlockedRequests;
66 private int easyPrivacyBlockedRequests;
67 private int fanboysAnnoyanceListBlockedRequests;
68 private int fanboysSocialBlockingListBlockedRequests;
69 private int ultraPrivacyBlockedRequests;
70 private int thirdPartyBlockedRequests;
72 // The pinned SSL certificate variables.
73 private boolean hasPinnedSslCertificate;
74 private String pinnedSslIssuedToCName;
75 private String pinnedSslIssuedToOName;
76 private String pinnedSslIssuedToUName;
77 private String pinnedSslIssuedByCName;
78 private String pinnedSslIssuedByOName;
79 private String pinnedSslIssuedByUName;
80 private Date pinnedSslStartDate;
81 private Date pinnedSslEndDate;
83 // The current IP addresses variables.
84 private boolean hasCurrentIpAddresses;
85 private String currentIpAddresses;
87 // The pinned IP addresses variables.
88 private boolean hasPinnedIpAddresses;
89 private String pinnedIpAddresses;
91 // 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.
92 private boolean ignorePinnedDomainInformation;
94 // The nested scrolling child helper is used throughout the class.
95 private NestedScrollingChildHelper nestedScrollingChildHelper;
97 // The previous Y position needs to be tracked between motion events.
98 private int previousYPosition;
102 // The basic constructor.
103 public NestedScrollWebView(Context context) {
104 // Roll up to the next constructor.
108 // The intermediate constructor.
109 public NestedScrollWebView(Context context, AttributeSet attributeSet) {
110 // Roll up to the next constructor.
111 this(context, attributeSet, android.R.attr.webViewStyle);
114 // The full constructor.
115 public NestedScrollWebView(Context context, AttributeSet attributeSet, int defaultStyle) {
116 // Run the default commands.
117 super(context, attributeSet, defaultStyle);
119 // Initialize the nested scrolling child helper.
120 nestedScrollingChildHelper = new NestedScrollingChildHelper(this);
122 // Enable nested scrolling by default.
123 nestedScrollingChildHelper.setNestedScrollingEnabled(true);
128 // WebView Fragment ID.
129 public void setWebViewFragmentId(long webViewFragmentId) {
130 // Store the WebView fragment ID.
131 this.webViewFragmentId = webViewFragmentId;
134 public long getWebViewFragmentId() {
135 // Return the WebView fragment ID.
136 return webViewFragmentId;
141 public void setDomainSettingsApplied(boolean applied) {
142 // Store the domain settings applied status.
143 domainSettingsApplied = applied;
146 public boolean getDomainSettingsApplied() {
147 // Return the domain settings applied status.
148 return domainSettingsApplied;
152 // Domain settings database ID.
153 public void setDomainSettingsDatabaseId(int databaseId) {
154 // Store the domain settings database ID.
155 domainSettingsDatabaseId = databaseId;
158 public int getDomainSettingsDatabaseId() {
159 // Return the domain settings database ID.
160 return domainSettingsDatabaseId;
164 // Current domain name. To function well when called, the domain name should never be allowed to be null.
165 public void setCurrentDomainName(@NonNull String domainName) {
166 // Store the current domain name.
167 currentDomainName = domainName;
170 public void resetCurrentDomainName() {
171 // Reset the current domain name.
172 currentDomainName = "";
175 public String getCurrentDomainName() {
176 // Return the current domain name.
177 return currentDomainName;
181 // Resource requests.
182 public void addResourceRequest(String[] resourceRequest) {
183 // Add the resource request to the list.
184 resourceRequests.add(resourceRequest);
187 public ArrayList<String[]> getResourceRequests() {
188 // Return the list of resource requests.
189 return resourceRequests;
192 public void clearResourceRequests() {
193 // Clear the resource requests.
194 resourceRequests.clear();
199 public void enableBlocklist(int blocklist, boolean status) {
200 // Update the status of the indicated blocklist.
203 // Update the status of the blocklist.
204 easyListEnabled = status;
208 // Update the status of the blocklist.
209 easyPrivacyEnabled = status;
212 case FANBOYS_ANNOYANCE_LIST:
213 // Update the status of the blocklist.
214 fanboysAnnoyanceListEnabled = status;
217 case FANBOYS_SOCIAL_BLOCKING_LIST:
218 // Update the status of the blocklist.
219 fanboysSocialBlockingListEnabled = status;
223 // Update the status of the blocklist.
224 ultraPrivacyEnabled = status;
227 case THIRD_PARTY_REQUESTS:
228 // Update the status of the blocklist.
229 blockAllThirdPartyRequests = status;
234 public boolean isBlocklistEnabled(int blocklist) {
235 // Get the status of the indicated blocklist.
238 // Return the status of the blocklist.
239 return easyListEnabled;
242 // Return the status of the blocklist.
243 return easyPrivacyEnabled;
245 case FANBOYS_ANNOYANCE_LIST:
246 // Return the status of the blocklist.
247 return fanboysAnnoyanceListEnabled;
249 case FANBOYS_SOCIAL_BLOCKING_LIST:
250 // Return the status of the blocklist.
251 return fanboysSocialBlockingListEnabled;
254 // Return the status of the blocklist.
255 return ultraPrivacyEnabled;
257 case THIRD_PARTY_REQUESTS:
258 // Return the status of the blocklist.
259 return blockAllThirdPartyRequests;
262 // The default value is required but should never be used.
268 // Resource request counters.
269 public void resetRequestsCounters() {
270 // Reset all the resource request counters.
272 easyListBlockedRequests = 0;
273 easyPrivacyBlockedRequests = 0;
274 fanboysAnnoyanceListBlockedRequests = 0;
275 fanboysSocialBlockingListBlockedRequests = 0;
276 ultraPrivacyBlockedRequests = 0;
277 thirdPartyBlockedRequests = 0;
280 public void incrementRequestsCount(int blocklist) {
281 // Increment the count of the indicated blocklist.
283 case BLOCKED_REQUESTS:
284 // Increment the blocked requests count.
289 // Increment the EasyList blocked requests count.
290 easyListBlockedRequests++;
294 // Increment the EasyPrivacy blocked requests count.
295 easyPrivacyBlockedRequests++;
298 case FANBOYS_ANNOYANCE_LIST:
299 // Increment the Fanboy's Annoyance List blocked requests count.
300 fanboysAnnoyanceListBlockedRequests++;
303 case FANBOYS_SOCIAL_BLOCKING_LIST:
304 // Increment the Fanboy's Social Blocking List blocked requests count.
305 fanboysSocialBlockingListBlockedRequests++;
309 // Increment the UltraPrivacy blocked requests count.
310 ultraPrivacyBlockedRequests++;
313 case THIRD_PARTY_REQUESTS:
314 // Increment the Third Party blocked requests count.
315 thirdPartyBlockedRequests++;
320 public int getRequestsCount(int blocklist) {
321 // Get the count of the indicated blocklist.
323 case BLOCKED_REQUESTS:
324 // Return the blocked requests count.
325 return blockedRequests;
328 // Return the EasyList blocked requests count.
329 return easyListBlockedRequests;
332 // Return the EasyPrivacy blocked requests count.
333 return easyPrivacyBlockedRequests;
335 case FANBOYS_ANNOYANCE_LIST:
336 // Return the Fanboy's Annoyance List blocked requests count.
337 return fanboysAnnoyanceListBlockedRequests;
339 case FANBOYS_SOCIAL_BLOCKING_LIST:
340 // Return the Fanboy's Social Blocking List blocked requests count.
341 return fanboysSocialBlockingListBlockedRequests;
344 // Return the UltraPrivacy blocked requests count.
345 return ultraPrivacyBlockedRequests;
347 case THIRD_PARTY_REQUESTS:
348 // Return the Third Party blocked requests count.
349 return thirdPartyBlockedRequests;
352 // Return 0. This should never end up being called.
358 // Pinned SSL certificates.
359 public boolean hasPinnedSslCertificate() {
360 // Return the status of the pinned SSL certificate.
361 return hasPinnedSslCertificate;
364 public void setPinnedSslCertificate(String issuedToCName, String issuedToOName, String issuedToUName, String issuedByCName, String issuedByOName, String issuedByUName, Date startDate, Date endDate) {
365 // Store the pinned SSL certificate information.
366 pinnedSslIssuedToCName = issuedToCName;
367 pinnedSslIssuedToOName = issuedToOName;
368 pinnedSslIssuedToUName = issuedToUName;
369 pinnedSslIssuedByCName = issuedByCName;
370 pinnedSslIssuedByOName = issuedByOName;
371 pinnedSslIssuedByUName = issuedByUName;
372 pinnedSslStartDate = startDate;
373 pinnedSslEndDate = endDate;
375 // Set the pinned SSL certificate tracker.
376 hasPinnedSslCertificate = true;
379 public ArrayList<Object> getPinnedSslCertificate() {
380 // Initialize an array list.
381 ArrayList<Object> arrayList = new ArrayList<>();
383 // Create the SSL certificate string array.
384 String[] sslCertificateStringArray = new String[] {pinnedSslIssuedToCName, pinnedSslIssuedToOName, pinnedSslIssuedToUName, pinnedSslIssuedByCName, pinnedSslIssuedByOName, pinnedSslIssuedByUName};
386 // Create the SSL certificate date array.
387 Date[] sslCertificateDateArray = new Date[] {pinnedSslStartDate, pinnedSslEndDate};
389 // Add the arrays to the array list.
390 arrayList.add(sslCertificateStringArray);
391 arrayList.add(sslCertificateDateArray);
393 // Return the pinned SSL certificate array list.
397 public void clearPinnedSslCertificate() {
398 // Clear the pinned SSL certificate.
399 pinnedSslIssuedToCName = null;
400 pinnedSslIssuedToOName = null;
401 pinnedSslIssuedToUName = null;
402 pinnedSslIssuedByCName = null;
403 pinnedSslIssuedByOName = null;
404 pinnedSslIssuedByUName = null;
405 pinnedSslStartDate = null;
406 pinnedSslEndDate = null;
408 // Clear the pinned SSL certificate tracker.
409 hasPinnedSslCertificate = false;
413 // Current IP addresses.
414 public boolean hasCurrentIpAddresses() {
415 // Return the status of the current IP addresses.
416 return hasCurrentIpAddresses;
419 public void setCurrentIpAddresses(String ipAddresses) {
420 // Store the current IP addresses.
421 currentIpAddresses = ipAddresses;
423 // Set the current IP addresses tracker.
424 hasCurrentIpAddresses = true;
427 public String getCurrentIpAddresses() {
428 // Return the current IP addresses.
429 return currentIpAddresses;
432 public void clearCurrentIpAddresses() {
433 // Clear the current IP addresses.
434 currentIpAddresses = null;
436 // Clear the current IP addresses tracker.
437 hasCurrentIpAddresses = false;
441 // Pinned IP addresses.
442 public boolean hasPinnedIpAddresses() {
443 // Return the status of the pinned IP addresses.
444 return hasPinnedIpAddresses;
447 public void setPinnedIpAddresses(String ipAddresses) {
448 // Store the pinned IP addresses.
449 pinnedIpAddresses = ipAddresses;
451 // Set the pinned IP addresses tracker.
452 hasPinnedIpAddresses = true;
455 public String getPinnedIpAddresses() {
456 // Return the pinned IP addresses.
457 return pinnedIpAddresses;
460 public void clearPinnedIpAddresses() {
461 // Clear the pinned IP addresses.
462 pinnedIpAddresses = null;
464 // Clear the pinned IP addresses tracker.
465 hasPinnedIpAddresses = false;
469 // Ignore pinned information. The syntax looks better as written, even if it is always inverted.
470 @SuppressWarnings("BooleanMethodIsAlwaysInverted")
471 public boolean ignorePinnedDomainInformation() {
472 // Return the status of the ignore pinned domain information tracker.
473 return ignorePinnedDomainInformation;
476 public void setIgnorePinnedDomainInformation(boolean status) {
477 // Set the status of the ignore pinned domain information tracker.
478 ignorePinnedDomainInformation = status;
484 public boolean onTouchEvent(MotionEvent motionEvent) {
485 // Initialize a tracker to return if this motion event is handled.
486 boolean motionEventHandled;
488 // Run the commands for the given motion event action.
489 switch (motionEvent.getAction()) {
490 case MotionEvent.ACTION_DOWN:
491 // Start nested scrolling along the vertical axis. `ViewCompat` must be used until the minimum API >= 21.
492 startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
494 // Save the current Y position. Action down will not be called again until a new motion starts.
495 previousYPosition = (int) motionEvent.getY();
497 // Run the default commands.
498 motionEventHandled = super.onTouchEvent(motionEvent);
501 case MotionEvent.ACTION_MOVE:
502 // Get the current Y position.
503 int currentYMotionPosition = (int) motionEvent.getY();
505 // Calculate the pre-scroll delta Y.
506 int preScrollDeltaY = previousYPosition - currentYMotionPosition;
508 // Initialize a variable to track how much of the scroll is consumed.
509 int[] consumedScroll = new int[2];
511 // Initialize a variable to track the offset in the window.
512 int[] offsetInWindow = new int[2];
514 // Get the WebView Y position.
515 int webViewYPosition = getScrollY();
517 // Set the scroll delta Y to initially be the same as the pre-scroll delta Y.
518 int scrollDeltaY = preScrollDeltaY;
520 // Dispatch the nested pre-school. This scrolls the app bar if it needs it. `offsetInWindow` will be returned with an updated value.
521 if (dispatchNestedPreScroll(0, preScrollDeltaY, consumedScroll, offsetInWindow)) {
522 // Update the scroll delta Y if some of it was consumed.
523 scrollDeltaY = preScrollDeltaY - consumedScroll[1];
526 // Check to see if the WebView is at the top and and the scroll action is downward.
527 if ((webViewYPosition == 0) && (scrollDeltaY < 0)) { // Swipe to refresh is being engaged.
528 // Stop the nested scroll so that swipe to refresh has complete control.
530 } else { // Swipe to refresh is not being engaged.
531 // Start the nested scroll so that the app bar can scroll off the screen.
532 startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
534 // 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.
535 dispatchNestedScroll(0, scrollDeltaY, 0, 0, offsetInWindow);
537 // Store the current Y position for use in the next action move.
538 previousYPosition = previousYPosition - scrollDeltaY;
541 // Run the default commands.
542 motionEventHandled = super.onTouchEvent(motionEvent);
547 // Stop nested scrolling.
550 // Run the default commands.
551 motionEventHandled = super.onTouchEvent(motionEvent);
554 // Perform a click. This is required by the Android accessibility guidelines.
557 // Return the status of the motion event.
558 return motionEventHandled;
561 // The Android accessibility guidelines require overriding `performClick()` and calling it from `onTouchEvent()`.
563 public boolean performClick() {
564 return super.performClick();
568 // Method from NestedScrollingChild.
570 public void setNestedScrollingEnabled(boolean status) {
571 // Set the status of the nested scrolling.
572 nestedScrollingChildHelper.setNestedScrollingEnabled(status);
575 // Method from NestedScrollingChild.
577 public boolean isNestedScrollingEnabled() {
578 // Return the status of nested scrolling.
579 return nestedScrollingChildHelper.isNestedScrollingEnabled();
583 // Method from NestedScrollingChild.
585 public boolean startNestedScroll(int axes) {
586 // Start a nested scroll along the indicated axes.
587 return nestedScrollingChildHelper.startNestedScroll(axes);
590 // Method from NestedScrollingChild2.
592 public boolean startNestedScroll(int axes, int type) {
593 // Start a nested scroll along the indicated axes for the given type of input which caused the scroll event.
594 return nestedScrollingChildHelper.startNestedScroll(axes, type);
598 // Method from NestedScrollingChild.
600 public void stopNestedScroll() {
601 // Stop the nested scroll.
602 nestedScrollingChildHelper.stopNestedScroll();
605 // Method from NestedScrollingChild2.
607 public void stopNestedScroll(int type) {
608 // Stop the nested scroll of the given type of input which caused the scroll event.
609 nestedScrollingChildHelper.stopNestedScroll(type);
613 // Method from NestedScrollingChild.
615 public boolean hasNestedScrollingParent() {
616 // Return the status of the nested scrolling parent.
617 return nestedScrollingChildHelper.hasNestedScrollingParent();
620 // Method from NestedScrollingChild2.
622 public boolean hasNestedScrollingParent(int type) {
623 // return the status of the nested scrolling parent for the given type of input which caused the scroll event.
624 return nestedScrollingChildHelper.hasNestedScrollingParent(type);
628 // Method from NestedScrollingChild.
630 public boolean dispatchNestedPreScroll(int deltaX, int deltaY, int[] consumed, int[] offsetInWindow) {
631 // Dispatch a nested pre-scroll with the specified deltas, which lets a parent to consume some of the scroll if desired.
632 return nestedScrollingChildHelper.dispatchNestedPreScroll(deltaX, deltaY, consumed, offsetInWindow);
635 // Method from NestedScrollingChild2.
637 public boolean dispatchNestedPreScroll(int deltaX, int deltaY, int[] consumed, int[] offsetInWindow, int type) {
638 // 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.
639 return nestedScrollingChildHelper.dispatchNestedPreScroll(deltaX, deltaY, consumed, offsetInWindow, type);
643 // Method from NestedScrollingChild.
645 public boolean dispatchNestedScroll(int deltaXConsumed, int deltaYConsumed, int deltaXUnconsumed, int deltaYUnconsumed, int[] offsetInWindow) {
646 // Dispatch a nested scroll with the specified deltas.
647 return nestedScrollingChildHelper.dispatchNestedScroll(deltaXConsumed, deltaYConsumed, deltaXUnconsumed, deltaYUnconsumed, offsetInWindow);
650 // Method from NestedScrollingChild2.
652 public boolean dispatchNestedScroll(int deltaXConsumed, int deltaYConsumed, int deltaXUnconsumed, int deltaYUnconsumed, int[] offsetInWindow, int type) {
653 // Dispatch a nested scroll with the specified deltas for the given type of input which caused the scroll event.
654 return nestedScrollingChildHelper.dispatchNestedScroll(deltaXConsumed, deltaYConsumed, deltaXUnconsumed, deltaYUnconsumed, offsetInWindow, type);
658 // Method from NestedScrollingChild.
660 public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
661 // Dispatch a nested pre-fling with the specified velocity, which lets a parent consume the fling if desired.
662 return nestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);
665 // Method from NestedScrollingChild.
667 public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
668 // Dispatch a nested fling with the specified velocity.
669 return nestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);