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.core.view.NestedScrollingChild2;
28 import androidx.core.view.NestedScrollingChildHelper;
29 import androidx.core.view.ViewCompat;
31 import java.util.ArrayList;
32 import java.util.Date;
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;
45 // Keep a copy of the WebView fragment ID.
46 private long webViewFragmentId;
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;
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;
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;
73 // The current IP addresses variables.
74 private boolean hasCurrentIpAddresses;
75 private String currentIpAddresses;
77 // The pinned IP addresses variables.
78 private boolean hasPinnedIpAddresses;
79 private String pinnedIpAddresses;
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;
84 // The nested scrolling child helper is used throughout the class.
85 private NestedScrollingChildHelper nestedScrollingChildHelper;
87 // The previous Y position needs to be tracked between motion events.
88 private int previousYPosition;
92 // The basic constructor.
93 public NestedScrollWebView(Context context) {
94 // Roll up to the next constructor.
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);
104 // The full constructor.
105 public NestedScrollWebView(Context context, AttributeSet attributeSet, int defaultStyle) {
106 // Run the default commands.
107 super(context, attributeSet, defaultStyle);
109 // Initialize the nested scrolling child helper.
110 nestedScrollingChildHelper = new NestedScrollingChildHelper(this);
112 // Enable nested scrolling by default.
113 nestedScrollingChildHelper.setNestedScrollingEnabled(true);
118 // WebView Fragment ID.
119 public void setWebViewFragmentId(long webViewFragmentId) {
120 // Store the WebView fragment ID.
121 this.webViewFragmentId = webViewFragmentId;
124 public long getWebViewFragmentId() {
125 // Return the WebView fragment ID.
126 return webViewFragmentId;
131 public void setDomainSettingsApplied(boolean applied) {
132 // Store the domain settings applied status.
133 domainSettingsApplied = applied;
136 public boolean getDomainSettingsApplied() {
137 // Return the domain settings applied status.
138 return domainSettingsApplied;
142 // Domain settings database ID.
143 public void setDomainSettingsDatabaseId(int databaseId) {
144 // Store the domain settings database ID.
145 domainSettingsDatabaseId = databaseId;
148 public int getDomainSettingsDatabaseId() {
149 // Return the domain settings database ID.
150 return domainSettingsDatabaseId;
154 // Resource requests.
155 public void addResourceRequest(String[] resourceRequest) {
156 // Add the resource request to the list.
157 resourceRequests.add(resourceRequest);
160 public ArrayList<String[]> getResourceRequests() {
161 // Return the list of resource requests.
162 return resourceRequests;
165 public void clearResourceRequests() {
166 // Clear the resource requests.
167 resourceRequests.clear();
171 // Resource request counters.
172 public void resetRequestsCount(int list) {
173 // Run the command on the indicated list.
175 case BLOCKED_REQUESTS:
176 // Reset the blocked requests count.
180 case EASY_LIST_BLOCKED_REQUESTS:
181 // Reset the EasyList blocked requests count.
182 easyListBlockedRequests = 0;
185 case EASY_PRIVACY_BLOCKED_REQUESTS:
186 // Reset the EasyPrivacy blocked requests count.
187 easyPrivacyBlockedRequests = 0;
190 case FANBOYS_ANNOYANCE_LIST_BLOCKED_REQUESTS:
191 // Reset the Fanboy's Annoyance List blocked requests count.
192 fanboysAnnoyanceListBlockedRequests = 0;
195 case FANBOYS_SOCIAL_BLOCKING_LIST_BLOCKED_REQUESTS:
196 // Reset the Fanboy's Social Blocking List blocked requests count.
197 fanboysSocialBlockingListBlockedRequests = 0;
200 case ULTRA_PRIVACY_BLOCKED_REQUESTS:
201 // Reset the UltraPrivacy blocked requests count.
202 ultraPrivacyBlockedRequests = 0;
205 case THIRD_PARTY_BLOCKED_REQUESTS:
206 // Reset the Third Party blocked requests count.
207 thirdPartyBlockedRequests = 0;
212 public void incrementRequestsCount(int list) {
213 // Run the command on the indicated list.
215 case BLOCKED_REQUESTS:
216 // Increment the blocked requests count.
220 case EASY_LIST_BLOCKED_REQUESTS:
221 // Increment the EasyList blocked requests count.
222 easyListBlockedRequests++;
225 case EASY_PRIVACY_BLOCKED_REQUESTS:
226 // Increment the EasyPrivacy blocked requests count.
227 easyPrivacyBlockedRequests++;
230 case FANBOYS_ANNOYANCE_LIST_BLOCKED_REQUESTS:
231 // Increment the Fanboy's Annoyance List blocked requests count.
232 fanboysAnnoyanceListBlockedRequests++;
235 case FANBOYS_SOCIAL_BLOCKING_LIST_BLOCKED_REQUESTS:
236 // Increment the Fanboy's Social Blocking List blocked requests count.
237 fanboysSocialBlockingListBlockedRequests++;
240 case ULTRA_PRIVACY_BLOCKED_REQUESTS:
241 // Increment the UltraPrivacy blocked requests count.
242 ultraPrivacyBlockedRequests++;
245 case THIRD_PARTY_BLOCKED_REQUESTS:
246 // Increment the Third Party blocked requests count.
247 thirdPartyBlockedRequests++;
252 public int getRequestsCount(int list) {
253 // Run the command on the indicated list.
255 case BLOCKED_REQUESTS:
256 // Return the blocked requests count.
257 return blockedRequests;
259 case EASY_LIST_BLOCKED_REQUESTS:
260 // Return the EasyList blocked requests count.
261 return easyListBlockedRequests;
263 case EASY_PRIVACY_BLOCKED_REQUESTS:
264 // Return the EasyPrivacy blocked requests count.
265 return easyPrivacyBlockedRequests;
267 case FANBOYS_ANNOYANCE_LIST_BLOCKED_REQUESTS:
268 // Return the Fanboy's Annoyance List blocked requests count.
269 return fanboysAnnoyanceListBlockedRequests;
271 case FANBOYS_SOCIAL_BLOCKING_LIST_BLOCKED_REQUESTS:
272 // Return the Fanboy's Social Blocking List blocked requests count.
273 return fanboysSocialBlockingListBlockedRequests;
275 case ULTRA_PRIVACY_BLOCKED_REQUESTS:
276 // Return the UltraPrivacy blocked requests count.
277 return ultraPrivacyBlockedRequests;
279 case THIRD_PARTY_BLOCKED_REQUESTS:
280 // Return the Third Party blocked requests count.
281 return thirdPartyBlockedRequests;
284 // Return 0. This should never end up being called.
290 // Pinned SSL certificates.
291 public boolean hasPinnedSslCertificate() {
292 // Return the status of the pinned SSL certificate.
293 return hasPinnedSslCertificate;
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;
307 // Set the pinned SSL certificate tracker.
308 hasPinnedSslCertificate = true;
311 public ArrayList<Object> getPinnedSslCertificate() {
312 // Initialize an array list.
313 ArrayList<Object> arrayList = new ArrayList<>();
315 // Create the SSL certificate string array.
316 String[] sslCertificateStringArray = new String[] {pinnedSslIssuedToCName, pinnedSslIssuedToOName, pinnedSslIssuedToUName, pinnedSslIssuedByCName, pinnedSslIssuedByOName, pinnedSslIssuedByUName};
318 // Create the SSL certificate date array.
319 Date[] sslCertificateDateArray = new Date[] {pinnedSslStartDate, pinnedSslEndDate};
321 // Add the arrays to the array list.
322 arrayList.add(sslCertificateStringArray);
323 arrayList.add(sslCertificateDateArray);
325 // Return the pinned SSL certificate array list.
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;
340 // Clear the pinned SSL certificate tracker.
341 hasPinnedSslCertificate = false;
345 // Current IP addresses.
346 public boolean hasCurrentIpAddresses() {
347 // Return the status of the current IP addresses.
348 return hasCurrentIpAddresses;
351 public void setCurrentIpAddresses(String ipAddresses) {
352 // Store the current IP addresses.
353 currentIpAddresses = ipAddresses;
355 // Set the current IP addresses tracker.
356 hasCurrentIpAddresses = true;
359 public String getCurrentIpAddresses() {
360 // Return the current IP addresses.
361 return currentIpAddresses;
364 public void clearCurrentIpAddresses() {
365 // Clear the current IP addresses.
366 currentIpAddresses = null;
368 // Clear the current IP addresses tracker.
369 hasCurrentIpAddresses = false;
373 // Pinned IP addresses.
374 public boolean hasPinnedIpAddresses() {
375 // Return the status of the pinned IP addresses.
376 return hasPinnedIpAddresses;
379 public void setPinnedIpAddresses(String ipAddresses) {
380 // Store the pinned IP addresses.
381 pinnedIpAddresses = ipAddresses;
383 // Set the pinned IP addresses tracker.
384 hasPinnedIpAddresses = true;
387 public String getPinnedIpAddresses() {
388 // Return the pinned IP addresses.
389 return pinnedIpAddresses;
392 public void clearPinnedIpAddresses() {
393 // Clear the pinned IP addresses.
394 pinnedIpAddresses = null;
396 // Clear the pinned IP addresses tracker.
397 hasPinnedIpAddresses = false;
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;
408 public void setIgnorePinnedDomainInformation(boolean status) {
409 // Set the status of the ignore pinned domain information tracker.
410 ignorePinnedDomainInformation = status;
416 public boolean onTouchEvent(MotionEvent motionEvent) {
417 // Initialize a tracker to return if this motion event is handled.
418 boolean motionEventHandled;
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);
426 // Save the current Y position. Action down will not be called again until a new motion starts.
427 previousYPosition = (int) motionEvent.getY();
429 // Run the default commands.
430 motionEventHandled = super.onTouchEvent(motionEvent);
433 case MotionEvent.ACTION_MOVE:
434 // Get the current Y position.
435 int currentYMotionPosition = (int) motionEvent.getY();
437 // Calculate the pre-scroll delta Y.
438 int preScrollDeltaY = previousYPosition - currentYMotionPosition;
440 // Initialize a variable to track how much of the scroll is consumed.
441 int[] consumedScroll = new int[2];
443 // Initialize a variable to track the offset in the window.
444 int[] offsetInWindow = new int[2];
446 // Get the WebView Y position.
447 int webViewYPosition = getScrollY();
449 // Set the scroll delta Y to initially be the same as the pre-scroll delta Y.
450 int scrollDeltaY = preScrollDeltaY;
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];
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.
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);
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);
469 // Store the current Y position for use in the next action move.
470 previousYPosition = previousYPosition - scrollDeltaY;
473 // Run the default commands.
474 motionEventHandled = super.onTouchEvent(motionEvent);
479 // Stop nested scrolling.
482 // Run the default commands.
483 motionEventHandled = super.onTouchEvent(motionEvent);
486 // Perform a click. This is required by the Android accessibility guidelines.
489 // Return the status of the motion event.
490 return motionEventHandled;
493 // The Android accessibility guidelines require overriding `performClick()` and calling it from `onTouchEvent()`.
495 public boolean performClick() {
496 return super.performClick();
500 // Method from NestedScrollingChild.
502 public void setNestedScrollingEnabled(boolean status) {
503 // Set the status of the nested scrolling.
504 nestedScrollingChildHelper.setNestedScrollingEnabled(status);
507 // Method from NestedScrollingChild.
509 public boolean isNestedScrollingEnabled() {
510 // Return the status of nested scrolling.
511 return nestedScrollingChildHelper.isNestedScrollingEnabled();
515 // Method from NestedScrollingChild.
517 public boolean startNestedScroll(int axes) {
518 // Start a nested scroll along the indicated axes.
519 return nestedScrollingChildHelper.startNestedScroll(axes);
522 // Method from NestedScrollingChild2.
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);
530 // Method from NestedScrollingChild.
532 public void stopNestedScroll() {
533 // Stop the nested scroll.
534 nestedScrollingChildHelper.stopNestedScroll();
537 // Method from NestedScrollingChild2.
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);
545 // Method from NestedScrollingChild.
547 public boolean hasNestedScrollingParent() {
548 // Return the status of the nested scrolling parent.
549 return nestedScrollingChildHelper.hasNestedScrollingParent();
552 // Method from NestedScrollingChild2.
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);
560 // Method from NestedScrollingChild.
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);
567 // Method from NestedScrollingChild2.
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);
575 // Method from NestedScrollingChild.
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);
582 // Method from NestedScrollingChild2.
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);
590 // Method from NestedScrollingChild.
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);
597 // Method from NestedScrollingChild.
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);