c4f54de9ee2488dc985a6ecdb0ba9fa2b599ccb1
[PrivacyBrowser.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
33 // NestedScrollWebView extends WebView to handle nested scrolls (scrolling the app bar off the screen).
34 public class NestedScrollWebView extends WebView implements NestedScrollingChild2 {
35     // These constants identify the blocklists.
36     public final static int BLOCKED_REQUESTS = 0;
37     public final static int EASY_LIST_BLOCKED_REQUESTS = 1;
38     public final static int EASY_PRIVACY_BLOCKED_REQUESTS = 2;
39     public final static int FANBOYS_ANNOYANCE_LIST_BLOCKED_REQUESTS = 3;
40     public final static int FANBOYS_SOCIAL_BLOCKING_LIST_BLOCKED_REQUESTS = 4;
41     public final static int ULTRA_PRIVACY_BLOCKED_REQUESTS = 5;
42     public final static int THIRD_PARTY_BLOCKED_REQUESTS = 6;
43
44     // The nested scrolling child helper is used throughout the class.
45     private NestedScrollingChildHelper nestedScrollingChildHelper;
46
47     // The previous Y position needs to be tracked between motion events.
48     private int previousYPosition;
49
50     // Track if domain settings are applied to this nested scroll WebView and, if so, the database ID.
51     private boolean domainSettingsApplied;
52     private int domainSettingsDatabaseId;
53
54     // Track the resource requests.
55     private ArrayList<String[]> resourceRequests = new ArrayList<>();
56     private int blockedRequests;
57     private int easyListBlockedRequests;
58     private int easyPrivacyBlockedRequests;
59     private int fanboysAnnoyanceListBlockedRequests;
60     private int fanboysSocialBlockingListBlockedRequests;
61     private int ultraPrivacyBlockedRequests;
62     private int thirdPartyBlockedRequests;
63
64     // Basic constructor.
65     public NestedScrollWebView(Context context) {
66         // Roll up to the next constructor.
67         this(context, null);
68     }
69
70     // Intermediate constructor.
71     public NestedScrollWebView(Context context, AttributeSet attributeSet) {
72         // Roll up to the next constructor.
73         this(context, attributeSet, android.R.attr.webViewStyle);
74     }
75
76     // Full constructor.
77     public NestedScrollWebView(Context context, AttributeSet attributeSet, int defaultStyle) {
78         // Run the default commands.
79         super(context, attributeSet, defaultStyle);
80
81         // Initialize the nested scrolling child helper.
82         nestedScrollingChildHelper = new NestedScrollingChildHelper(this);
83
84         // Enable nested scrolling by default.
85         nestedScrollingChildHelper.setNestedScrollingEnabled(true);
86     }
87
88     public void setDomainSettingsApplied(boolean applied) {
89         // Store the domain settings applied status.
90         domainSettingsApplied = applied;
91     }
92
93     public boolean getDomainSettingsApplied() {
94         // Return the domain settings applied status.
95         return domainSettingsApplied;
96     }
97
98     public void setDomainSettingsDatabaseId(int databaseId) {
99         // Store the domain settings database ID.
100         domainSettingsDatabaseId = databaseId;
101     }
102
103     public int getDomainSettingsDatabaseId() {
104         // Return the domain settings database ID.
105         return domainSettingsDatabaseId;
106     }
107
108     public void addResourceRequest(String[] resourceRequest) {
109         // Add the resource request to the list.
110         resourceRequests.add(resourceRequest);
111     }
112
113     public ArrayList<String[]> getResourceRequests() {
114         // Return the list of resource requests.
115         return resourceRequests;
116     }
117
118     public void clearResourceRequests() {
119         // Clear the resource requests.
120         resourceRequests.clear();
121     }
122
123     public void resetRequestsCount(int list) {
124         // Run the command on the indicated list.
125         switch (list) {
126             case BLOCKED_REQUESTS:
127                 // Reset the blocked requests count.
128                 blockedRequests = 0;
129                 break;
130
131             case EASY_LIST_BLOCKED_REQUESTS:
132                 // Reset the EasyList blocked requests count.
133                 easyListBlockedRequests = 0;
134                 break;
135
136             case EASY_PRIVACY_BLOCKED_REQUESTS:
137                 // Reset the EasyPrivacy blocked requests count.
138                 easyPrivacyBlockedRequests = 0;
139                 break;
140
141             case FANBOYS_ANNOYANCE_LIST_BLOCKED_REQUESTS:
142                 // Reset the Fanboy's Annoyance List blocked requests count.
143                 fanboysAnnoyanceListBlockedRequests = 0;
144                 break;
145
146             case FANBOYS_SOCIAL_BLOCKING_LIST_BLOCKED_REQUESTS:
147                 // Reset the Fanboy's Social Blocking List blocked requests count.
148                 fanboysSocialBlockingListBlockedRequests = 0;
149                 break;
150
151             case ULTRA_PRIVACY_BLOCKED_REQUESTS:
152                 // Reset the UltraPrivacy blocked requests count.
153                 ultraPrivacyBlockedRequests = 0;
154                 break;
155
156             case THIRD_PARTY_BLOCKED_REQUESTS:
157                 // Reset the Third Party blocked requests count.
158                 thirdPartyBlockedRequests = 0;
159                 break;
160         }
161     }
162
163     public void incrementRequestsCount(int list) {
164         // Run the command on the indicated list.
165         switch (list) {
166             case BLOCKED_REQUESTS:
167                 // Increment the blocked requests count.
168                 blockedRequests++;
169                 break;
170
171             case EASY_LIST_BLOCKED_REQUESTS:
172                 // Increment the EasyList blocked requests count.
173                 easyListBlockedRequests++;
174                 break;
175
176             case EASY_PRIVACY_BLOCKED_REQUESTS:
177                 // Increment the EasyPrivacy blocked requests count.
178                 easyPrivacyBlockedRequests++;
179                 break;
180
181             case FANBOYS_ANNOYANCE_LIST_BLOCKED_REQUESTS:
182                 // Increment the Fanboy's Annoyance List blocked requests count.
183                 fanboysAnnoyanceListBlockedRequests++;
184                 break;
185
186             case FANBOYS_SOCIAL_BLOCKING_LIST_BLOCKED_REQUESTS:
187                 // Increment the Fanboy's Social Blocking List blocked requests count.
188                 fanboysSocialBlockingListBlockedRequests++;
189                 break;
190
191             case ULTRA_PRIVACY_BLOCKED_REQUESTS:
192                 // Increment the UltraPrivacy blocked requests count.
193                 ultraPrivacyBlockedRequests++;
194                 break;
195
196             case THIRD_PARTY_BLOCKED_REQUESTS:
197                 // Increment the Third Party blocked requests count.
198                 thirdPartyBlockedRequests++;
199                 break;
200         }
201     }
202
203     public int getRequestsCount(int list) {
204         // Run the command on the indicated list.
205         switch (list) {
206             case BLOCKED_REQUESTS:
207                 // Return the blocked requests count.
208                 return blockedRequests;
209
210             case EASY_LIST_BLOCKED_REQUESTS:
211                 // Return the EasyList blocked requests count.
212                 return easyListBlockedRequests;
213
214             case EASY_PRIVACY_BLOCKED_REQUESTS:
215                 // Return the EasyPrivacy blocked requests count.
216                 return easyPrivacyBlockedRequests;
217
218             case FANBOYS_ANNOYANCE_LIST_BLOCKED_REQUESTS:
219                 // Return the Fanboy's Annoyance List blocked requests count.
220                 return fanboysAnnoyanceListBlockedRequests;
221
222             case FANBOYS_SOCIAL_BLOCKING_LIST_BLOCKED_REQUESTS:
223                 // Return the Fanboy's Social Blocking List blocked requests count.
224                 return fanboysSocialBlockingListBlockedRequests;
225
226             case ULTRA_PRIVACY_BLOCKED_REQUESTS:
227                 // Return the UltraPrivacy blocked requests count.
228                 return ultraPrivacyBlockedRequests;
229
230             case THIRD_PARTY_BLOCKED_REQUESTS:
231                 // Return the Third Party blocked requests count.
232                 return thirdPartyBlockedRequests;
233
234             default:
235                 // Return 0.  This should never end up being called.
236                 return 0;
237         }
238     }
239
240     @Override
241     public boolean onTouchEvent(MotionEvent motionEvent) {
242         // Initialize a tracker to return if this motion event is handled.
243         boolean motionEventHandled;
244
245         // Run the commands for the given motion event action.
246         switch (motionEvent.getAction()) {
247             case MotionEvent.ACTION_DOWN:
248                 // Start nested scrolling along the vertical axis.  `ViewCompat` must be used until the minimum API >= 21.
249                 startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
250
251                 // Save the current Y position.  Action down will not be called again until a new motion starts.
252                 previousYPosition = (int) motionEvent.getY();
253
254                 // Run the default commands.
255                 motionEventHandled = super.onTouchEvent(motionEvent);
256                 break;
257
258             case MotionEvent.ACTION_MOVE:
259                 // Get the current Y position.
260                 int currentYMotionPosition = (int) motionEvent.getY();
261
262                 // Calculate the pre-scroll delta Y.
263                 int preScrollDeltaY = previousYPosition - currentYMotionPosition;
264
265                 // Initialize a variable to track how much of the scroll is consumed.
266                 int[] consumedScroll = new int[2];
267
268                 // Initialize a variable to track the offset in the window.
269                 int[] offsetInWindow = new int[2];
270
271                 // Get the WebView Y position.
272                 int webViewYPosition = getScrollY();
273
274                 // Set the scroll delta Y to initially be the same as the pre-scroll delta Y.
275                 int scrollDeltaY = preScrollDeltaY;
276
277                 // Dispatch the nested pre-school.  This scrolls the app bar if it needs it.  `offsetInWindow` will be returned with an updated value.
278                 if (dispatchNestedPreScroll(0, preScrollDeltaY, consumedScroll, offsetInWindow)) {
279                     // Update the scroll delta Y if some of it was consumed.
280                     scrollDeltaY = preScrollDeltaY - consumedScroll[1];
281                 }
282
283                 // Check to see if the WebView is at the top and and the scroll action is downward.
284                 if ((webViewYPosition == 0) && (scrollDeltaY < 0)) {  // Swipe to refresh is being engaged.
285                     // Stop the nested scroll so that swipe to refresh has complete control.
286                     stopNestedScroll();
287                 } else {  // Swipe to refresh is not being engaged.
288                     // Start the nested scroll so that the app bar can scroll off the screen.
289                     startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
290
291                     // 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.
292                     dispatchNestedScroll(0, scrollDeltaY, 0, 0, offsetInWindow);
293
294                     // Store the current Y position for use in the next action move.
295                     previousYPosition = previousYPosition - scrollDeltaY;
296                 }
297
298                 // Run the default commands.
299                 motionEventHandled = super.onTouchEvent(motionEvent);
300                 break;
301
302
303             default:
304                 // Stop nested scrolling.
305                 stopNestedScroll();
306
307                 // Run the default commands.
308                 motionEventHandled = super.onTouchEvent(motionEvent);
309         }
310
311         // Perform a click.  This is required by the Android accessibility guidelines.
312         performClick();
313
314         // Return the status of the motion event.
315         return motionEventHandled;
316     }
317
318     // The Android accessibility guidelines require overriding `performClick()` and calling it from `onTouchEvent()`.
319     @Override
320     public boolean performClick() {
321         return super.performClick();
322     }
323
324
325     // Method from NestedScrollingChild.
326     @Override
327     public void setNestedScrollingEnabled(boolean status) {
328         // Set the status of the nested scrolling.
329         nestedScrollingChildHelper.setNestedScrollingEnabled(status);
330     }
331
332     // Method from NestedScrollingChild.
333     @Override
334     public boolean isNestedScrollingEnabled() {
335         // Return the status of nested scrolling.
336         return nestedScrollingChildHelper.isNestedScrollingEnabled();
337     }
338
339
340     // Method from NestedScrollingChild.
341     @Override
342     public boolean startNestedScroll(int axes) {
343         // Start a nested scroll along the indicated axes.
344         return nestedScrollingChildHelper.startNestedScroll(axes);
345     }
346
347     // Method from NestedScrollingChild2.
348     @Override
349     public boolean startNestedScroll(int axes, int type) {
350         // Start a nested scroll along the indicated axes for the given type of input which caused the scroll event.
351         return nestedScrollingChildHelper.startNestedScroll(axes, type);
352     }
353
354
355     // Method from NestedScrollingChild.
356     @Override
357     public void stopNestedScroll() {
358         // Stop the nested scroll.
359         nestedScrollingChildHelper.stopNestedScroll();
360     }
361
362     // Method from NestedScrollingChild2.
363     @Override
364     public void stopNestedScroll(int type) {
365         // Stop the nested scroll of the given type of input which caused the scroll event.
366         nestedScrollingChildHelper.stopNestedScroll(type);
367     }
368
369
370     // Method from NestedScrollingChild.
371     @Override
372     public boolean hasNestedScrollingParent() {
373         // Return the status of the nested scrolling parent.
374         return nestedScrollingChildHelper.hasNestedScrollingParent();
375     }
376
377     // Method from NestedScrollingChild2.
378     @Override
379     public boolean hasNestedScrollingParent(int type) {
380         // return the status of the nested scrolling parent for the given type of input which caused the scroll event.
381         return nestedScrollingChildHelper.hasNestedScrollingParent(type);
382     }
383
384
385     // Method from NestedScrollingChild.
386     @Override
387     public boolean dispatchNestedPreScroll(int deltaX, int deltaY, int[] consumed, int[] offsetInWindow) {
388         // Dispatch a nested pre-scroll with the specified deltas, which lets a parent to consume some of the scroll if desired.
389         return nestedScrollingChildHelper.dispatchNestedPreScroll(deltaX, deltaY, consumed, offsetInWindow);
390     }
391
392     // Method from NestedScrollingChild2.
393     @Override
394     public boolean dispatchNestedPreScroll(int deltaX, int deltaY, int[] consumed, int[] offsetInWindow, int type) {
395         // 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.
396         return nestedScrollingChildHelper.dispatchNestedPreScroll(deltaX, deltaY, consumed, offsetInWindow, type);
397     }
398
399
400     // Method from NestedScrollingChild.
401     @Override
402     public boolean dispatchNestedScroll(int deltaXConsumed, int deltaYConsumed, int deltaXUnconsumed, int deltaYUnconsumed, int[] offsetInWindow) {
403         // Dispatch a nested scroll with the specified deltas.
404         return nestedScrollingChildHelper.dispatchNestedScroll(deltaXConsumed, deltaYConsumed, deltaXUnconsumed, deltaYUnconsumed, offsetInWindow);
405     }
406
407     // Method from NestedScrollingChild2.
408     @Override
409     public boolean dispatchNestedScroll(int deltaXConsumed, int deltaYConsumed, int deltaXUnconsumed, int deltaYUnconsumed, int[] offsetInWindow, int type) {
410         // Dispatch a nested scroll with the specified deltas for the given type of input which caused the scroll event.
411         return nestedScrollingChildHelper.dispatchNestedScroll(deltaXConsumed, deltaYConsumed, deltaXUnconsumed, deltaYUnconsumed, offsetInWindow, type);
412     }
413
414
415     // Method from NestedScrollingChild.
416     @Override
417     public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
418         // Dispatch a nested pre-fling with the specified velocity, which lets a parent consume the fling if desired.
419         return nestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);
420     }
421
422     // Method from NestedScrollingChild.
423     @Override
424     public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
425         // Dispatch a nested fling with the specified velocity.
426         return nestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
427     }
428 }