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.graphics.Bitmap;
24 import android.graphics.drawable.BitmapDrawable;
25 import android.graphics.drawable.Drawable;
26 import android.util.AttributeSet;
27 import android.view.MotionEvent;
28 import android.webkit.WebView;
30 import com.stoutner.privacybrowser.R;
32 import androidx.annotation.NonNull;
33 import androidx.core.content.ContextCompat;
34 import androidx.core.view.NestedScrollingChild2;
35 import androidx.core.view.NestedScrollingChildHelper;
36 import androidx.core.view.ViewCompat;
38 import java.util.ArrayList;
39 import java.util.Date;
41 // NestedScrollWebView extends WebView to handle nested scrolls (scrolling the app bar off the screen).
42 public class NestedScrollWebView extends WebView implements NestedScrollingChild2 {
43 // These constants identify the blocklists.
44 public final static int BLOCKED_REQUESTS = 0;
45 public final static int EASY_LIST = 1;
46 public final static int EASY_PRIVACY = 2;
47 public final static int FANBOYS_ANNOYANCE_LIST = 3;
48 public final static int FANBOYS_SOCIAL_BLOCKING_LIST = 4;
49 public final static int ULTRA_PRIVACY = 5;
50 public final static int THIRD_PARTY_REQUESTS = 6;
52 // Keep a copy of the WebView fragment ID.
53 private long webViewFragmentId;
55 // Track if domain settings are applied to this nested scroll WebView and, if so, the database ID.
56 private boolean domainSettingsApplied;
57 private int domainSettingsDatabaseId;
59 // Keep track of when the domain name changes so that domain settings can be reapplied. This should never be null.
60 private String currentDomainName = "";
62 // Track the resource requests.
63 private ArrayList<String[]> resourceRequests = new ArrayList<>();
64 private boolean easyListEnabled;
65 private boolean easyPrivacyEnabled;
66 private boolean fanboysAnnoyanceListEnabled;
67 private boolean fanboysSocialBlockingListEnabled;
68 private boolean ultraPrivacyEnabled;
69 private boolean blockAllThirdPartyRequests;
70 private int blockedRequests;
71 private int easyListBlockedRequests;
72 private int easyPrivacyBlockedRequests;
73 private int fanboysAnnoyanceListBlockedRequests;
74 private int fanboysSocialBlockingListBlockedRequests;
75 private int ultraPrivacyBlockedRequests;
76 private int thirdPartyBlockedRequests;
78 // The pinned SSL certificate variables.
79 private boolean hasPinnedSslCertificate;
80 private String pinnedSslIssuedToCName;
81 private String pinnedSslIssuedToOName;
82 private String pinnedSslIssuedToUName;
83 private String pinnedSslIssuedByCName;
84 private String pinnedSslIssuedByOName;
85 private String pinnedSslIssuedByUName;
86 private Date pinnedSslStartDate;
87 private Date pinnedSslEndDate;
89 // The current IP addresses variables.
90 private boolean hasCurrentIpAddresses;
91 private String currentIpAddresses;
93 // The pinned IP addresses variables.
94 private boolean hasPinnedIpAddresses;
95 private String pinnedIpAddresses;
97 // 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.
98 private boolean ignorePinnedDomainInformation;
100 // The default or favorite icon.
101 Bitmap favoriteOrDefaultIcon;
103 // The nested scrolling child helper is used throughout the class.
104 private NestedScrollingChildHelper nestedScrollingChildHelper;
106 // The previous Y position needs to be tracked between motion events.
107 private int previousYPosition;
111 // The basic constructor.
112 public NestedScrollWebView(Context context) {
113 // Roll up to the next constructor.
117 // The intermediate constructor.
118 public NestedScrollWebView(Context context, AttributeSet attributeSet) {
119 // Roll up to the next constructor.
120 this(context, attributeSet, android.R.attr.webViewStyle);
123 // The full constructor.
124 public NestedScrollWebView(Context context, AttributeSet attributeSet, int defaultStyle) {
125 // Run the default commands.
126 super(context, attributeSet, defaultStyle);
128 // Initialize the nested scrolling child helper.
129 nestedScrollingChildHelper = new NestedScrollingChildHelper(this);
131 // Enable nested scrolling by default.
132 nestedScrollingChildHelper.setNestedScrollingEnabled(true);
137 // WebView Fragment ID.
138 public void setWebViewFragmentId(long webViewFragmentId) {
139 // Store the WebView fragment ID.
140 this.webViewFragmentId = webViewFragmentId;
143 public long getWebViewFragmentId() {
144 // Return the WebView fragment ID.
145 return webViewFragmentId;
150 public void setDomainSettingsApplied(boolean applied) {
151 // Store the domain settings applied status.
152 domainSettingsApplied = applied;
155 public boolean getDomainSettingsApplied() {
156 // Return the domain settings applied status.
157 return domainSettingsApplied;
161 // Domain settings database ID.
162 public void setDomainSettingsDatabaseId(int databaseId) {
163 // Store the domain settings database ID.
164 domainSettingsDatabaseId = databaseId;
167 public int getDomainSettingsDatabaseId() {
168 // Return the domain settings database ID.
169 return domainSettingsDatabaseId;
173 // Current domain name. To function well when called, the domain name should never be allowed to be null.
174 public void setCurrentDomainName(@NonNull String domainName) {
175 // Store the current domain name.
176 currentDomainName = domainName;
179 public void resetCurrentDomainName() {
180 // Reset the current domain name.
181 currentDomainName = "";
184 public String getCurrentDomainName() {
185 // Return the current domain name.
186 return currentDomainName;
190 // Resource requests.
191 public void addResourceRequest(String[] resourceRequest) {
192 // Add the resource request to the list.
193 resourceRequests.add(resourceRequest);
196 public ArrayList<String[]> getResourceRequests() {
197 // Return the list of resource requests.
198 return resourceRequests;
201 public void clearResourceRequests() {
202 // Clear the resource requests.
203 resourceRequests.clear();
208 public void enableBlocklist(int blocklist, boolean status) {
209 // Update the status of the indicated blocklist.
212 // Update the status of the blocklist.
213 easyListEnabled = status;
217 // Update the status of the blocklist.
218 easyPrivacyEnabled = status;
221 case FANBOYS_ANNOYANCE_LIST:
222 // Update the status of the blocklist.
223 fanboysAnnoyanceListEnabled = status;
226 case FANBOYS_SOCIAL_BLOCKING_LIST:
227 // Update the status of the blocklist.
228 fanboysSocialBlockingListEnabled = status;
232 // Update the status of the blocklist.
233 ultraPrivacyEnabled = status;
236 case THIRD_PARTY_REQUESTS:
237 // Update the status of the blocklist.
238 blockAllThirdPartyRequests = status;
243 public boolean isBlocklistEnabled(int blocklist) {
244 // Get the status of the indicated blocklist.
247 // Return the status of the blocklist.
248 return easyListEnabled;
251 // Return the status of the blocklist.
252 return easyPrivacyEnabled;
254 case FANBOYS_ANNOYANCE_LIST:
255 // Return the status of the blocklist.
256 return fanboysAnnoyanceListEnabled;
258 case FANBOYS_SOCIAL_BLOCKING_LIST:
259 // Return the status of the blocklist.
260 return fanboysSocialBlockingListEnabled;
263 // Return the status of the blocklist.
264 return ultraPrivacyEnabled;
266 case THIRD_PARTY_REQUESTS:
267 // Return the status of the blocklist.
268 return blockAllThirdPartyRequests;
271 // The default value is required but should never be used.
277 // Resource request counters.
278 public void resetRequestsCounters() {
279 // Reset all the resource request counters.
281 easyListBlockedRequests = 0;
282 easyPrivacyBlockedRequests = 0;
283 fanboysAnnoyanceListBlockedRequests = 0;
284 fanboysSocialBlockingListBlockedRequests = 0;
285 ultraPrivacyBlockedRequests = 0;
286 thirdPartyBlockedRequests = 0;
289 public void incrementRequestsCount(int blocklist) {
290 // Increment the count of the indicated blocklist.
292 case BLOCKED_REQUESTS:
293 // Increment the blocked requests count.
298 // Increment the EasyList blocked requests count.
299 easyListBlockedRequests++;
303 // Increment the EasyPrivacy blocked requests count.
304 easyPrivacyBlockedRequests++;
307 case FANBOYS_ANNOYANCE_LIST:
308 // Increment the Fanboy's Annoyance List blocked requests count.
309 fanboysAnnoyanceListBlockedRequests++;
312 case FANBOYS_SOCIAL_BLOCKING_LIST:
313 // Increment the Fanboy's Social Blocking List blocked requests count.
314 fanboysSocialBlockingListBlockedRequests++;
318 // Increment the UltraPrivacy blocked requests count.
319 ultraPrivacyBlockedRequests++;
322 case THIRD_PARTY_REQUESTS:
323 // Increment the Third Party blocked requests count.
324 thirdPartyBlockedRequests++;
329 public int getRequestsCount(int blocklist) {
330 // Get the count of the indicated blocklist.
332 case BLOCKED_REQUESTS:
333 // Return the blocked requests count.
334 return blockedRequests;
337 // Return the EasyList blocked requests count.
338 return easyListBlockedRequests;
341 // Return the EasyPrivacy blocked requests count.
342 return easyPrivacyBlockedRequests;
344 case FANBOYS_ANNOYANCE_LIST:
345 // Return the Fanboy's Annoyance List blocked requests count.
346 return fanboysAnnoyanceListBlockedRequests;
348 case FANBOYS_SOCIAL_BLOCKING_LIST:
349 // Return the Fanboy's Social Blocking List blocked requests count.
350 return fanboysSocialBlockingListBlockedRequests;
353 // Return the UltraPrivacy blocked requests count.
354 return ultraPrivacyBlockedRequests;
356 case THIRD_PARTY_REQUESTS:
357 // Return the Third Party blocked requests count.
358 return thirdPartyBlockedRequests;
361 // Return 0. This should never end up being called.
367 // Pinned SSL certificates.
368 public boolean hasPinnedSslCertificate() {
369 // Return the status of the pinned SSL certificate.
370 return hasPinnedSslCertificate;
373 public void setPinnedSslCertificate(String issuedToCName, String issuedToOName, String issuedToUName, String issuedByCName, String issuedByOName, String issuedByUName, Date startDate, Date endDate) {
374 // Store the pinned SSL certificate information.
375 pinnedSslIssuedToCName = issuedToCName;
376 pinnedSslIssuedToOName = issuedToOName;
377 pinnedSslIssuedToUName = issuedToUName;
378 pinnedSslIssuedByCName = issuedByCName;
379 pinnedSslIssuedByOName = issuedByOName;
380 pinnedSslIssuedByUName = issuedByUName;
381 pinnedSslStartDate = startDate;
382 pinnedSslEndDate = endDate;
384 // Set the pinned SSL certificate tracker.
385 hasPinnedSslCertificate = true;
388 public ArrayList<Object> getPinnedSslCertificate() {
389 // Initialize an array list.
390 ArrayList<Object> arrayList = new ArrayList<>();
392 // Create the SSL certificate string array.
393 String[] sslCertificateStringArray = new String[] {pinnedSslIssuedToCName, pinnedSslIssuedToOName, pinnedSslIssuedToUName, pinnedSslIssuedByCName, pinnedSslIssuedByOName, pinnedSslIssuedByUName};
395 // Create the SSL certificate date array.
396 Date[] sslCertificateDateArray = new Date[] {pinnedSslStartDate, pinnedSslEndDate};
398 // Add the arrays to the array list.
399 arrayList.add(sslCertificateStringArray);
400 arrayList.add(sslCertificateDateArray);
402 // Return the pinned SSL certificate array list.
406 public void clearPinnedSslCertificate() {
407 // Clear the pinned SSL certificate.
408 pinnedSslIssuedToCName = null;
409 pinnedSslIssuedToOName = null;
410 pinnedSslIssuedToUName = null;
411 pinnedSslIssuedByCName = null;
412 pinnedSslIssuedByOName = null;
413 pinnedSslIssuedByUName = null;
414 pinnedSslStartDate = null;
415 pinnedSslEndDate = null;
417 // Clear the pinned SSL certificate tracker.
418 hasPinnedSslCertificate = false;
422 // Current IP addresses.
423 public boolean hasCurrentIpAddresses() {
424 // Return the status of the current IP addresses.
425 return hasCurrentIpAddresses;
428 public void setCurrentIpAddresses(String ipAddresses) {
429 // Store the current IP addresses.
430 currentIpAddresses = ipAddresses;
432 // Set the current IP addresses tracker.
433 hasCurrentIpAddresses = true;
436 public String getCurrentIpAddresses() {
437 // Return the current IP addresses.
438 return currentIpAddresses;
441 public void clearCurrentIpAddresses() {
442 // Clear the current IP addresses.
443 currentIpAddresses = null;
445 // Clear the current IP addresses tracker.
446 hasCurrentIpAddresses = false;
450 // Pinned IP addresses.
451 public boolean hasPinnedIpAddresses() {
452 // Return the status of the pinned IP addresses.
453 return hasPinnedIpAddresses;
456 public void setPinnedIpAddresses(String ipAddresses) {
457 // Store the pinned IP addresses.
458 pinnedIpAddresses = ipAddresses;
460 // Set the pinned IP addresses tracker.
461 hasPinnedIpAddresses = true;
464 public String getPinnedIpAddresses() {
465 // Return the pinned IP addresses.
466 return pinnedIpAddresses;
469 public void clearPinnedIpAddresses() {
470 // Clear the pinned IP addresses.
471 pinnedIpAddresses = null;
473 // Clear the pinned IP addresses tracker.
474 hasPinnedIpAddresses = false;
478 // Ignore pinned information. The syntax looks better as written, even if it is always inverted.
479 @SuppressWarnings("BooleanMethodIsAlwaysInverted")
480 public boolean ignorePinnedDomainInformation() {
481 // Return the status of the ignore pinned domain information tracker.
482 return ignorePinnedDomainInformation;
485 public void setIgnorePinnedDomainInformation(boolean status) {
486 // Set the status of the ignore pinned domain information tracker.
487 ignorePinnedDomainInformation = status;
491 // Favorite or default icon.
492 public void initializeFavoriteIcon() {
493 // Get the default favorite icon drawable. `ContextCompat` must be used until API >= 21.
494 Drawable favoriteIconDrawable = ContextCompat.getDrawable(getContext(), R.drawable.world);
496 // Cast the favorite icon drawable to a bitmap drawable.
497 BitmapDrawable favoriteIconBitmapDrawable = (BitmapDrawable) favoriteIconDrawable;
499 // Remove the incorrect warning below that the favorite icon bitmap drawable might be null.
500 assert favoriteIconBitmapDrawable != null;
502 // Store the default icon bitmap.
503 favoriteOrDefaultIcon = favoriteIconBitmapDrawable.getBitmap();
506 public void setFavoriteOrDefaultIcon(Bitmap icon) {
507 // Scale the favorite icon bitmap down if it is larger than 256 x 256. Filtering uses bilinear interpolation.
508 if ((icon.getHeight() > 256) || (icon.getWidth() > 256)) {
509 favoriteOrDefaultIcon = Bitmap.createScaledBitmap(icon, 256, 256, true);
511 // Store the icon as presented.
512 favoriteOrDefaultIcon = icon;
516 public Bitmap getFavoriteOrDefaultIcon() {
517 // Return the favorite or default icon.
518 return favoriteOrDefaultIcon;
524 public boolean onTouchEvent(MotionEvent motionEvent) {
525 // Initialize a tracker to return if this motion event is handled.
526 boolean motionEventHandled;
528 // Run the commands for the given motion event action.
529 switch (motionEvent.getAction()) {
530 case MotionEvent.ACTION_DOWN:
531 // Start nested scrolling along the vertical axis. `ViewCompat` must be used until the minimum API >= 21.
532 startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
534 // Save the current Y position. Action down will not be called again until a new motion starts.
535 previousYPosition = (int) motionEvent.getY();
537 // Run the default commands.
538 motionEventHandled = super.onTouchEvent(motionEvent);
541 case MotionEvent.ACTION_MOVE:
542 // Get the current Y position.
543 int currentYMotionPosition = (int) motionEvent.getY();
545 // Calculate the pre-scroll delta Y.
546 int preScrollDeltaY = previousYPosition - currentYMotionPosition;
548 // Initialize a variable to track how much of the scroll is consumed.
549 int[] consumedScroll = new int[2];
551 // Initialize a variable to track the offset in the window.
552 int[] offsetInWindow = new int[2];
554 // Get the WebView Y position.
555 int webViewYPosition = getScrollY();
557 // Set the scroll delta Y to initially be the same as the pre-scroll delta Y.
558 int scrollDeltaY = preScrollDeltaY;
560 // Dispatch the nested pre-school. This scrolls the app bar if it needs it. `offsetInWindow` will be returned with an updated value.
561 if (dispatchNestedPreScroll(0, preScrollDeltaY, consumedScroll, offsetInWindow)) {
562 // Update the scroll delta Y if some of it was consumed.
563 scrollDeltaY = preScrollDeltaY - consumedScroll[1];
566 // Check to see if the WebView is at the top and and the scroll action is downward.
567 if ((webViewYPosition == 0) && (scrollDeltaY < 0)) { // Swipe to refresh is being engaged.
568 // Stop the nested scroll so that swipe to refresh has complete control.
570 } else { // Swipe to refresh is not being engaged.
571 // Start the nested scroll so that the app bar can scroll off the screen.
572 startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
574 // 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.
575 dispatchNestedScroll(0, scrollDeltaY, 0, 0, offsetInWindow);
577 // Store the current Y position for use in the next action move.
578 previousYPosition = previousYPosition - scrollDeltaY;
581 // Run the default commands.
582 motionEventHandled = super.onTouchEvent(motionEvent);
587 // Stop nested scrolling.
590 // Run the default commands.
591 motionEventHandled = super.onTouchEvent(motionEvent);
594 // Perform a click. This is required by the Android accessibility guidelines.
597 // Return the status of the motion event.
598 return motionEventHandled;
601 // The Android accessibility guidelines require overriding `performClick()` and calling it from `onTouchEvent()`.
603 public boolean performClick() {
604 return super.performClick();
608 // Method from NestedScrollingChild.
610 public void setNestedScrollingEnabled(boolean status) {
611 // Set the status of the nested scrolling.
612 nestedScrollingChildHelper.setNestedScrollingEnabled(status);
615 // Method from NestedScrollingChild.
617 public boolean isNestedScrollingEnabled() {
618 // Return the status of nested scrolling.
619 return nestedScrollingChildHelper.isNestedScrollingEnabled();
623 // Method from NestedScrollingChild.
625 public boolean startNestedScroll(int axes) {
626 // Start a nested scroll along the indicated axes.
627 return nestedScrollingChildHelper.startNestedScroll(axes);
630 // Method from NestedScrollingChild2.
632 public boolean startNestedScroll(int axes, int type) {
633 // Start a nested scroll along the indicated axes for the given type of input which caused the scroll event.
634 return nestedScrollingChildHelper.startNestedScroll(axes, type);
638 // Method from NestedScrollingChild.
640 public void stopNestedScroll() {
641 // Stop the nested scroll.
642 nestedScrollingChildHelper.stopNestedScroll();
645 // Method from NestedScrollingChild2.
647 public void stopNestedScroll(int type) {
648 // Stop the nested scroll of the given type of input which caused the scroll event.
649 nestedScrollingChildHelper.stopNestedScroll(type);
653 // Method from NestedScrollingChild.
655 public boolean hasNestedScrollingParent() {
656 // Return the status of the nested scrolling parent.
657 return nestedScrollingChildHelper.hasNestedScrollingParent();
660 // Method from NestedScrollingChild2.
662 public boolean hasNestedScrollingParent(int type) {
663 // return the status of the nested scrolling parent for the given type of input which caused the scroll event.
664 return nestedScrollingChildHelper.hasNestedScrollingParent(type);
668 // Method from NestedScrollingChild.
670 public boolean dispatchNestedPreScroll(int deltaX, int deltaY, int[] consumed, int[] offsetInWindow) {
671 // Dispatch a nested pre-scroll with the specified deltas, which lets a parent to consume some of the scroll if desired.
672 return nestedScrollingChildHelper.dispatchNestedPreScroll(deltaX, deltaY, consumed, offsetInWindow);
675 // Method from NestedScrollingChild2.
677 public boolean dispatchNestedPreScroll(int deltaX, int deltaY, int[] consumed, int[] offsetInWindow, int type) {
678 // 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.
679 return nestedScrollingChildHelper.dispatchNestedPreScroll(deltaX, deltaY, consumed, offsetInWindow, type);
683 // Method from NestedScrollingChild.
685 public boolean dispatchNestedScroll(int deltaXConsumed, int deltaYConsumed, int deltaXUnconsumed, int deltaYUnconsumed, int[] offsetInWindow) {
686 // Dispatch a nested scroll with the specified deltas.
687 return nestedScrollingChildHelper.dispatchNestedScroll(deltaXConsumed, deltaYConsumed, deltaXUnconsumed, deltaYUnconsumed, offsetInWindow);
690 // Method from NestedScrollingChild2.
692 public boolean dispatchNestedScroll(int deltaXConsumed, int deltaYConsumed, int deltaXUnconsumed, int deltaYUnconsumed, int[] offsetInWindow, int type) {
693 // Dispatch a nested scroll with the specified deltas for the given type of input which caused the scroll event.
694 return nestedScrollingChildHelper.dispatchNestedScroll(deltaXConsumed, deltaYConsumed, deltaXUnconsumed, deltaYUnconsumed, offsetInWindow, type);
698 // Method from NestedScrollingChild.
700 public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
701 // Dispatch a nested pre-fling with the specified velocity, which lets a parent consume the fling if desired.
702 return nestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);
705 // Method from NestedScrollingChild.
707 public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
708 // Dispatch a nested fling with the specified velocity.
709 return nestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);