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_BLOCKED_REQUESTS = 1;
40 public final static int EASY_PRIVACY_BLOCKED_REQUESTS = 2;
41 public final static int FANBOYS_ANNOYANCE_LIST_BLOCKED_REQUESTS = 3;
42 public final static int FANBOYS_SOCIAL_BLOCKING_LIST_BLOCKED_REQUESTS = 4;
43 public final static int ULTRA_PRIVACY_BLOCKED_REQUESTS = 5;
44 public final static int THIRD_PARTY_BLOCKED_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 int blockedRequests;
59 private int easyListBlockedRequests;
60 private int easyPrivacyBlockedRequests;
61 private int fanboysAnnoyanceListBlockedRequests;
62 private int fanboysSocialBlockingListBlockedRequests;
63 private int ultraPrivacyBlockedRequests;
64 private int thirdPartyBlockedRequests;
66 // The pinned SSL certificate variables.
67 private boolean hasPinnedSslCertificate;
68 private String pinnedSslIssuedToCName;
69 private String pinnedSslIssuedToOName;
70 private String pinnedSslIssuedToUName;
71 private String pinnedSslIssuedByCName;
72 private String pinnedSslIssuedByOName;
73 private String pinnedSslIssuedByUName;
74 private Date pinnedSslStartDate;
75 private Date pinnedSslEndDate;
77 // The current IP addresses variables.
78 private boolean hasCurrentIpAddresses;
79 private String currentIpAddresses;
81 // The pinned IP addresses variables.
82 private boolean hasPinnedIpAddresses;
83 private String pinnedIpAddresses;
85 // 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.
86 private boolean ignorePinnedDomainInformation;
88 // The nested scrolling child helper is used throughout the class.
89 private NestedScrollingChildHelper nestedScrollingChildHelper;
91 // The previous Y position needs to be tracked between motion events.
92 private int previousYPosition;
96 // The basic constructor.
97 public NestedScrollWebView(Context context) {
98 // Roll up to the next constructor.
102 // The intermediate constructor.
103 public NestedScrollWebView(Context context, AttributeSet attributeSet) {
104 // Roll up to the next constructor.
105 this(context, attributeSet, android.R.attr.webViewStyle);
108 // The full constructor.
109 public NestedScrollWebView(Context context, AttributeSet attributeSet, int defaultStyle) {
110 // Run the default commands.
111 super(context, attributeSet, defaultStyle);
113 // Initialize the nested scrolling child helper.
114 nestedScrollingChildHelper = new NestedScrollingChildHelper(this);
116 // Enable nested scrolling by default.
117 nestedScrollingChildHelper.setNestedScrollingEnabled(true);
122 // WebView Fragment ID.
123 public void setWebViewFragmentId(long webViewFragmentId) {
124 // Store the WebView fragment ID.
125 this.webViewFragmentId = webViewFragmentId;
128 public long getWebViewFragmentId() {
129 // Return the WebView fragment ID.
130 return webViewFragmentId;
135 public void setDomainSettingsApplied(boolean applied) {
136 // Store the domain settings applied status.
137 domainSettingsApplied = applied;
140 public boolean getDomainSettingsApplied() {
141 // Return the domain settings applied status.
142 return domainSettingsApplied;
146 // Domain settings database ID.
147 public void setDomainSettingsDatabaseId(int databaseId) {
148 // Store the domain settings database ID.
149 domainSettingsDatabaseId = databaseId;
152 public int getDomainSettingsDatabaseId() {
153 // Return the domain settings database ID.
154 return domainSettingsDatabaseId;
158 // Current domain name. To function well when called, the domain name should never be allowed to be null.
159 public void setCurrentDomainName(@NonNull String domainName) {
160 // Store the current domain name.
161 currentDomainName = domainName;
164 public void resetCurrentDomainName() {
165 // Reset the current domain name.
166 currentDomainName = "";
169 public String getCurrentDomainName() {
170 // Return the current domain name.
171 return currentDomainName;
175 // Resource requests.
176 public void addResourceRequest(String[] resourceRequest) {
177 // Add the resource request to the list.
178 resourceRequests.add(resourceRequest);
181 public ArrayList<String[]> getResourceRequests() {
182 // Return the list of resource requests.
183 return resourceRequests;
186 public void clearResourceRequests() {
187 // Clear the resource requests.
188 resourceRequests.clear();
192 // Resource request counters.
193 public void resetRequestsCount(int list) {
194 // Run the command on the indicated list.
196 case BLOCKED_REQUESTS:
197 // Reset the blocked requests count.
201 case EASY_LIST_BLOCKED_REQUESTS:
202 // Reset the EasyList blocked requests count.
203 easyListBlockedRequests = 0;
206 case EASY_PRIVACY_BLOCKED_REQUESTS:
207 // Reset the EasyPrivacy blocked requests count.
208 easyPrivacyBlockedRequests = 0;
211 case FANBOYS_ANNOYANCE_LIST_BLOCKED_REQUESTS:
212 // Reset the Fanboy's Annoyance List blocked requests count.
213 fanboysAnnoyanceListBlockedRequests = 0;
216 case FANBOYS_SOCIAL_BLOCKING_LIST_BLOCKED_REQUESTS:
217 // Reset the Fanboy's Social Blocking List blocked requests count.
218 fanboysSocialBlockingListBlockedRequests = 0;
221 case ULTRA_PRIVACY_BLOCKED_REQUESTS:
222 // Reset the UltraPrivacy blocked requests count.
223 ultraPrivacyBlockedRequests = 0;
226 case THIRD_PARTY_BLOCKED_REQUESTS:
227 // Reset the Third Party blocked requests count.
228 thirdPartyBlockedRequests = 0;
233 public void incrementRequestsCount(int list) {
234 // Run the command on the indicated list.
236 case BLOCKED_REQUESTS:
237 // Increment the blocked requests count.
241 case EASY_LIST_BLOCKED_REQUESTS:
242 // Increment the EasyList blocked requests count.
243 easyListBlockedRequests++;
246 case EASY_PRIVACY_BLOCKED_REQUESTS:
247 // Increment the EasyPrivacy blocked requests count.
248 easyPrivacyBlockedRequests++;
251 case FANBOYS_ANNOYANCE_LIST_BLOCKED_REQUESTS:
252 // Increment the Fanboy's Annoyance List blocked requests count.
253 fanboysAnnoyanceListBlockedRequests++;
256 case FANBOYS_SOCIAL_BLOCKING_LIST_BLOCKED_REQUESTS:
257 // Increment the Fanboy's Social Blocking List blocked requests count.
258 fanboysSocialBlockingListBlockedRequests++;
261 case ULTRA_PRIVACY_BLOCKED_REQUESTS:
262 // Increment the UltraPrivacy blocked requests count.
263 ultraPrivacyBlockedRequests++;
266 case THIRD_PARTY_BLOCKED_REQUESTS:
267 // Increment the Third Party blocked requests count.
268 thirdPartyBlockedRequests++;
273 public int getRequestsCount(int list) {
274 // Run the command on the indicated list.
276 case BLOCKED_REQUESTS:
277 // Return the blocked requests count.
278 return blockedRequests;
280 case EASY_LIST_BLOCKED_REQUESTS:
281 // Return the EasyList blocked requests count.
282 return easyListBlockedRequests;
284 case EASY_PRIVACY_BLOCKED_REQUESTS:
285 // Return the EasyPrivacy blocked requests count.
286 return easyPrivacyBlockedRequests;
288 case FANBOYS_ANNOYANCE_LIST_BLOCKED_REQUESTS:
289 // Return the Fanboy's Annoyance List blocked requests count.
290 return fanboysAnnoyanceListBlockedRequests;
292 case FANBOYS_SOCIAL_BLOCKING_LIST_BLOCKED_REQUESTS:
293 // Return the Fanboy's Social Blocking List blocked requests count.
294 return fanboysSocialBlockingListBlockedRequests;
296 case ULTRA_PRIVACY_BLOCKED_REQUESTS:
297 // Return the UltraPrivacy blocked requests count.
298 return ultraPrivacyBlockedRequests;
300 case THIRD_PARTY_BLOCKED_REQUESTS:
301 // Return the Third Party blocked requests count.
302 return thirdPartyBlockedRequests;
305 // Return 0. This should never end up being called.
311 // Pinned SSL certificates.
312 public boolean hasPinnedSslCertificate() {
313 // Return the status of the pinned SSL certificate.
314 return hasPinnedSslCertificate;
317 public void setPinnedSslCertificate(String issuedToCName, String issuedToOName, String issuedToUName, String issuedByCName, String issuedByOName, String issuedByUName, Date startDate, Date endDate) {
318 // Store the pinned SSL certificate information.
319 pinnedSslIssuedToCName = issuedToCName;
320 pinnedSslIssuedToOName = issuedToOName;
321 pinnedSslIssuedToUName = issuedToUName;
322 pinnedSslIssuedByCName = issuedByCName;
323 pinnedSslIssuedByOName = issuedByOName;
324 pinnedSslIssuedByUName = issuedByUName;
325 pinnedSslStartDate = startDate;
326 pinnedSslEndDate = endDate;
328 // Set the pinned SSL certificate tracker.
329 hasPinnedSslCertificate = true;
332 public ArrayList<Object> getPinnedSslCertificate() {
333 // Initialize an array list.
334 ArrayList<Object> arrayList = new ArrayList<>();
336 // Create the SSL certificate string array.
337 String[] sslCertificateStringArray = new String[] {pinnedSslIssuedToCName, pinnedSslIssuedToOName, pinnedSslIssuedToUName, pinnedSslIssuedByCName, pinnedSslIssuedByOName, pinnedSslIssuedByUName};
339 // Create the SSL certificate date array.
340 Date[] sslCertificateDateArray = new Date[] {pinnedSslStartDate, pinnedSslEndDate};
342 // Add the arrays to the array list.
343 arrayList.add(sslCertificateStringArray);
344 arrayList.add(sslCertificateDateArray);
346 // Return the pinned SSL certificate array list.
350 public void clearPinnedSslCertificate() {
351 // Clear the pinned SSL certificate.
352 pinnedSslIssuedToCName = null;
353 pinnedSslIssuedToOName = null;
354 pinnedSslIssuedToUName = null;
355 pinnedSslIssuedByCName = null;
356 pinnedSslIssuedByOName = null;
357 pinnedSslIssuedByUName = null;
358 pinnedSslStartDate = null;
359 pinnedSslEndDate = null;
361 // Clear the pinned SSL certificate tracker.
362 hasPinnedSslCertificate = false;
366 // Current IP addresses.
367 public boolean hasCurrentIpAddresses() {
368 // Return the status of the current IP addresses.
369 return hasCurrentIpAddresses;
372 public void setCurrentIpAddresses(String ipAddresses) {
373 // Store the current IP addresses.
374 currentIpAddresses = ipAddresses;
376 // Set the current IP addresses tracker.
377 hasCurrentIpAddresses = true;
380 public String getCurrentIpAddresses() {
381 // Return the current IP addresses.
382 return currentIpAddresses;
385 public void clearCurrentIpAddresses() {
386 // Clear the current IP addresses.
387 currentIpAddresses = null;
389 // Clear the current IP addresses tracker.
390 hasCurrentIpAddresses = false;
394 // Pinned IP addresses.
395 public boolean hasPinnedIpAddresses() {
396 // Return the status of the pinned IP addresses.
397 return hasPinnedIpAddresses;
400 public void setPinnedIpAddresses(String ipAddresses) {
401 // Store the pinned IP addresses.
402 pinnedIpAddresses = ipAddresses;
404 // Set the pinned IP addresses tracker.
405 hasPinnedIpAddresses = true;
408 public String getPinnedIpAddresses() {
409 // Return the pinned IP addresses.
410 return pinnedIpAddresses;
413 public void clearPinnedIpAddresses() {
414 // Clear the pinned IP addresses.
415 pinnedIpAddresses = null;
417 // Clear the pinned IP addresses tracker.
418 hasPinnedIpAddresses = false;
422 // Ignore pinned information. The syntax looks better as written, even if it is always inverted.
423 @SuppressWarnings("BooleanMethodIsAlwaysInverted")
424 public boolean ignorePinnedDomainInformation() {
425 // Return the status of the ignore pinned domain information tracker.
426 return ignorePinnedDomainInformation;
429 public void setIgnorePinnedDomainInformation(boolean status) {
430 // Set the status of the ignore pinned domain information tracker.
431 ignorePinnedDomainInformation = status;
437 public boolean onTouchEvent(MotionEvent motionEvent) {
438 // Initialize a tracker to return if this motion event is handled.
439 boolean motionEventHandled;
441 // Run the commands for the given motion event action.
442 switch (motionEvent.getAction()) {
443 case MotionEvent.ACTION_DOWN:
444 // Start nested scrolling along the vertical axis. `ViewCompat` must be used until the minimum API >= 21.
445 startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
447 // Save the current Y position. Action down will not be called again until a new motion starts.
448 previousYPosition = (int) motionEvent.getY();
450 // Run the default commands.
451 motionEventHandled = super.onTouchEvent(motionEvent);
454 case MotionEvent.ACTION_MOVE:
455 // Get the current Y position.
456 int currentYMotionPosition = (int) motionEvent.getY();
458 // Calculate the pre-scroll delta Y.
459 int preScrollDeltaY = previousYPosition - currentYMotionPosition;
461 // Initialize a variable to track how much of the scroll is consumed.
462 int[] consumedScroll = new int[2];
464 // Initialize a variable to track the offset in the window.
465 int[] offsetInWindow = new int[2];
467 // Get the WebView Y position.
468 int webViewYPosition = getScrollY();
470 // Set the scroll delta Y to initially be the same as the pre-scroll delta Y.
471 int scrollDeltaY = preScrollDeltaY;
473 // Dispatch the nested pre-school. This scrolls the app bar if it needs it. `offsetInWindow` will be returned with an updated value.
474 if (dispatchNestedPreScroll(0, preScrollDeltaY, consumedScroll, offsetInWindow)) {
475 // Update the scroll delta Y if some of it was consumed.
476 scrollDeltaY = preScrollDeltaY - consumedScroll[1];
479 // Check to see if the WebView is at the top and and the scroll action is downward.
480 if ((webViewYPosition == 0) && (scrollDeltaY < 0)) { // Swipe to refresh is being engaged.
481 // Stop the nested scroll so that swipe to refresh has complete control.
483 } else { // Swipe to refresh is not being engaged.
484 // Start the nested scroll so that the app bar can scroll off the screen.
485 startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
487 // 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.
488 dispatchNestedScroll(0, scrollDeltaY, 0, 0, offsetInWindow);
490 // Store the current Y position for use in the next action move.
491 previousYPosition = previousYPosition - scrollDeltaY;
494 // Run the default commands.
495 motionEventHandled = super.onTouchEvent(motionEvent);
500 // Stop nested scrolling.
503 // Run the default commands.
504 motionEventHandled = super.onTouchEvent(motionEvent);
507 // Perform a click. This is required by the Android accessibility guidelines.
510 // Return the status of the motion event.
511 return motionEventHandled;
514 // The Android accessibility guidelines require overriding `performClick()` and calling it from `onTouchEvent()`.
516 public boolean performClick() {
517 return super.performClick();
521 // Method from NestedScrollingChild.
523 public void setNestedScrollingEnabled(boolean status) {
524 // Set the status of the nested scrolling.
525 nestedScrollingChildHelper.setNestedScrollingEnabled(status);
528 // Method from NestedScrollingChild.
530 public boolean isNestedScrollingEnabled() {
531 // Return the status of nested scrolling.
532 return nestedScrollingChildHelper.isNestedScrollingEnabled();
536 // Method from NestedScrollingChild.
538 public boolean startNestedScroll(int axes) {
539 // Start a nested scroll along the indicated axes.
540 return nestedScrollingChildHelper.startNestedScroll(axes);
543 // Method from NestedScrollingChild2.
545 public boolean startNestedScroll(int axes, int type) {
546 // Start a nested scroll along the indicated axes for the given type of input which caused the scroll event.
547 return nestedScrollingChildHelper.startNestedScroll(axes, type);
551 // Method from NestedScrollingChild.
553 public void stopNestedScroll() {
554 // Stop the nested scroll.
555 nestedScrollingChildHelper.stopNestedScroll();
558 // Method from NestedScrollingChild2.
560 public void stopNestedScroll(int type) {
561 // Stop the nested scroll of the given type of input which caused the scroll event.
562 nestedScrollingChildHelper.stopNestedScroll(type);
566 // Method from NestedScrollingChild.
568 public boolean hasNestedScrollingParent() {
569 // Return the status of the nested scrolling parent.
570 return nestedScrollingChildHelper.hasNestedScrollingParent();
573 // Method from NestedScrollingChild2.
575 public boolean hasNestedScrollingParent(int type) {
576 // return the status of the nested scrolling parent for the given type of input which caused the scroll event.
577 return nestedScrollingChildHelper.hasNestedScrollingParent(type);
581 // Method from NestedScrollingChild.
583 public boolean dispatchNestedPreScroll(int deltaX, int deltaY, int[] consumed, int[] offsetInWindow) {
584 // Dispatch a nested pre-scroll with the specified deltas, which lets a parent to consume some of the scroll if desired.
585 return nestedScrollingChildHelper.dispatchNestedPreScroll(deltaX, deltaY, consumed, offsetInWindow);
588 // Method from NestedScrollingChild2.
590 public boolean dispatchNestedPreScroll(int deltaX, int deltaY, int[] consumed, int[] offsetInWindow, int type) {
591 // 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.
592 return nestedScrollingChildHelper.dispatchNestedPreScroll(deltaX, deltaY, consumed, offsetInWindow, type);
596 // Method from NestedScrollingChild.
598 public boolean dispatchNestedScroll(int deltaXConsumed, int deltaYConsumed, int deltaXUnconsumed, int deltaYUnconsumed, int[] offsetInWindow) {
599 // Dispatch a nested scroll with the specified deltas.
600 return nestedScrollingChildHelper.dispatchNestedScroll(deltaXConsumed, deltaYConsumed, deltaXUnconsumed, deltaYUnconsumed, offsetInWindow);
603 // Method from NestedScrollingChild2.
605 public boolean dispatchNestedScroll(int deltaXConsumed, int deltaYConsumed, int deltaXUnconsumed, int deltaYUnconsumed, int[] offsetInWindow, int type) {
606 // Dispatch a nested scroll with the specified deltas for the given type of input which caused the scroll event.
607 return nestedScrollingChildHelper.dispatchNestedScroll(deltaXConsumed, deltaYConsumed, deltaXUnconsumed, deltaYUnconsumed, offsetInWindow, type);
611 // Method from NestedScrollingChild.
613 public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
614 // Dispatch a nested pre-fling with the specified velocity, which lets a parent consume the fling if desired.
615 return nestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);
618 // Method from NestedScrollingChild.
620 public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
621 // Dispatch a nested fling with the specified velocity.
622 return nestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);