13 | Apache License
14 | Version 2.0, January 2004
15 | http://www.apache.org/licenses/
16 |
18 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 19 |
20 | 21 |22 | "License" shall mean the terms and conditions for use, reproduction, 23 | and distribution as defined by Sections 1 through 9 of this document. 24 |
25 |26 | "Licensor" shall mean the copyright owner or entity authorized by 27 | the copyright owner that is granting the License. 28 |
29 |30 | "Legal Entity" shall mean the union of the acting entity and all 31 | other entities that control, are controlled by, or are under common 32 | control with that entity. For the purposes of this definition, 33 | "control" means (i) the power, direct or indirect, to cause the 34 | direction or management of such entity, whether by contract or 35 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 36 | outstanding shares, or (iii) beneficial ownership of such entity. 37 |
38 |39 | "You" (or "Your") shall mean an individual or Legal Entity 40 | exercising permissions granted by this License. 41 |
42 |43 | "Source" form shall mean the preferred form for making modifications, 44 | including but not limited to software source code, documentation 45 | source, and configuration files. 46 |
47 |48 | "Object" form shall mean any form resulting from mechanical 49 | transformation or translation of a Source form, including but 50 | not limited to compiled object code, generated documentation, 51 | and conversions to other media types. 52 |
53 |54 | "Work" shall mean the work of authorship, whether in Source or 55 | Object form, made available under the License, as indicated by a 56 | copyright notice that is included in or attached to the work 57 | (an example is provided in the Appendix below). 58 |
59 |60 | "Derivative Works" shall mean any work, whether in Source or Object 61 | form, that is based on (or derived from) the Work and for which the 62 | editorial revisions, annotations, elaborations, or other modifications 63 | represent, as a whole, an original work of authorship. For the purposes 64 | of this License, Derivative Works shall not include works that remain 65 | separable from, or merely link (or bind by name) to the interfaces of, 66 | the Work and Derivative Works thereof. 67 |
68 |69 | "Contribution" shall mean any work of authorship, including 70 | the original version of the Work and any modifications or additions 71 | to that Work or Derivative Works thereof, that is intentionally 72 | submitted to Licensor for inclusion in the Work by the copyright owner 73 | or by an individual or Legal Entity authorized to submit on behalf of 74 | the copyright owner. For the purposes of this definition, "submitted" 75 | means any form of electronic, verbal, or written communication sent 76 | to the Licensor or its representatives, including but not limited to 77 | communication on electronic mailing lists, source code control systems, 78 | and issue tracking systems that are managed by, or on behalf of, the 79 | Licensor for the purpose of discussing and improving the Work, but 80 | excluding communication that is conspicuously marked or otherwise 81 | designated in writing by the copyright owner as "Not a Contribution." 82 |
83 |84 | "Contributor" shall mean Licensor and any individual or Legal Entity 85 | on behalf of whom a Contribution has been received by Licensor and 86 | subsequently incorporated within the Work. 87 |
88 |2. Grant of Copyright License. 89 | Subject to the terms and conditions of 90 | this License, each Contributor hereby grants to You a perpetual, 91 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 92 | copyright license to reproduce, prepare Derivative Works of, 93 | publicly display, publicly perform, sublicense, and distribute the 94 | Work and such Derivative Works in Source or Object form. 95 |
96 |3. Grant of Patent License. 97 | Subject to the terms and conditions of 98 | this License, each Contributor hereby grants to You a perpetual, 99 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 100 | (except as stated in this section) patent license to make, have made, 101 | use, offer to sell, sell, import, and otherwise transfer the Work, 102 | where such license applies only to those patent claims licensable 103 | by such Contributor that are necessarily infringed by their 104 | Contribution(s) alone or by combination of their Contribution(s) 105 | with the Work to which such Contribution(s) was submitted. If You 106 | institute patent litigation against any entity (including a 107 | cross-claim or counterclaim in a lawsuit) alleging that the Work 108 | or a Contribution incorporated within the Work constitutes direct 109 | or contributory patent infringement, then any patent licenses 110 | granted to You under this License for that Work shall terminate 111 | as of the date such litigation is filed. 112 |
113 |4. Redistribution. 114 | You may reproduce and distribute copies of the 115 | Work or Derivative Works thereof in any medium, with or without 116 | modifications, and in Source or Object form, provided that You 117 | meet the following conditions: 118 |
119 |5. Submission of Contributions. 161 | Unless You explicitly state otherwise, 162 | any Contribution intentionally submitted for inclusion in the Work 163 | by You to the Licensor shall be under the terms and conditions of 164 | this License, without any additional terms or conditions. 165 | Notwithstanding the above, nothing herein shall supersede or modify 166 | the terms of any separate license agreement you may have executed 167 | with Licensor regarding such Contributions. 168 |
169 |6. Trademarks. 170 | This License does not grant permission to use the trade 171 | names, trademarks, service marks, or product names of the Licensor, 172 | except as required for reasonable and customary use in describing the 173 | origin of the Work and reproducing the content of the NOTICE file. 174 |
175 |7. Disclaimer of Warranty. 176 | Unless required by applicable law or 177 | agreed to in writing, Licensor provides the Work (and each 178 | Contributor provides its Contributions) on an "AS IS" BASIS, 179 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 180 | implied, including, without limitation, any warranties or conditions 181 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 182 | PARTICULAR PURPOSE. You are solely responsible for determining the 183 | appropriateness of using or redistributing the Work and assume any 184 | risks associated with Your exercise of permissions under this License. 185 |
186 |8. Limitation of Liability. 187 | In no event and under no legal theory, 188 | whether in tort (including negligence), contract, or otherwise, 189 | unless required by applicable law (such as deliberate and grossly 190 | negligent acts) or agreed to in writing, shall any Contributor be 191 | liable to You for damages, including any direct, indirect, special, 192 | incidental, or consequential damages of any character arising as a 193 | result of this License or out of the use or inability to use the 194 | Work (including but not limited to damages for loss of goodwill, 195 | work stoppage, computer failure or malfunction, or any and all 196 | other commercial damages or losses), even if such Contributor 197 | has been advised of the possibility of such damages. 198 |
199 |9. Accepting Warranty or Additional Liability. 200 | While redistributing 201 | the Work or Derivative Works thereof, You may choose to offer, 202 | and charge a fee for, acceptance of support, warranty, indemnity, 203 | or other liability obligations and/or rights consistent with this 204 | License. However, in accepting such obligations, You may act only 205 | on Your own behalf and on Your sole responsibility, not on behalf 206 | of any other Contributor, and only if You agree to indemnify, 207 | defend, and hold each Contributor harmless for any liability 208 | incurred by, or claims asserted against, such Contributor by reason 209 | of your accepting any such warranty or additional liability. 210 |
211 |212 | END OF TERMS AND CONDITIONS 213 |
214 | 215 | -------------------------------------------------------------------------------- /library/src/main/java/uk/co/alt236/webviewdebug/webviewclient/DebugWebViewClientLogger.java: -------------------------------------------------------------------------------- 1 | package uk.co.alt236.webviewdebug.webviewclient; 2 | 3 | import android.annotation.TargetApi; 4 | import android.graphics.Bitmap; 5 | import android.net.Uri; 6 | import android.net.http.SslError; 7 | import android.os.Build; 8 | import android.os.Message; 9 | import android.view.InputEvent; 10 | import android.view.KeyEvent; 11 | import android.webkit.ClientCertRequest; 12 | import android.webkit.HttpAuthHandler; 13 | import android.webkit.RenderProcessGoneDetail; 14 | import android.webkit.SafeBrowsingResponse; 15 | import android.webkit.SslErrorHandler; 16 | import android.webkit.WebResourceError; 17 | import android.webkit.WebResourceRequest; 18 | import android.webkit.WebResourceResponse; 19 | import android.webkit.WebView; 20 | 21 | import java.util.Locale; 22 | 23 | import androidx.annotation.NonNull; 24 | import androidx.annotation.RequiresApi; 25 | import androidx.annotation.VisibleForTesting; 26 | import uk.co.alt236.webviewdebug.BuildConfig; 27 | 28 | @SuppressWarnings("WeakerAccess") 29 | public class DebugWebViewClientLogger implements LogControl { 30 | private static final Locale LOCALE = Locale.US; 31 | private static final String IN = "--->"; 32 | private static final String OUT = "<---"; 33 | private static final String SPACE = " "; 34 | private static final String DEFAULT_TAG = BuildConfig.DEFAULT_LOG_TAG; 35 | 36 | private final LogEngine logger; 37 | private boolean loggingEnabled; 38 | private boolean logKeyEventsEnabled; 39 | 40 | public DebugWebViewClientLogger() { 41 | this(DEFAULT_TAG); 42 | } 43 | 44 | public DebugWebViewClientLogger(@NonNull final String tag) { 45 | this(new LogEngine(tag)); 46 | } 47 | 48 | @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) 49 | protected DebugWebViewClientLogger(@NonNull final LogEngine logEngine) { 50 | this.logger = logEngine; 51 | } 52 | 53 | @RequiresApi(api = Build.VERSION_CODES.M) 54 | public void onReceivedError(final WebView view, final WebResourceRequest request, final WebResourceError error) { 55 | if (loggingEnabled) { 56 | final Uri url = request.getUrl(); 57 | final String method = request.getMethod(); 58 | final int code = error.getErrorCode(); 59 | 60 | logger.logError(String.format(LOCALE, "%s onReceivedError() 1/3 CALL : %d %s %s", SPACE, code, method, url)); 61 | logger.logError(String.format(LOCALE, "%s onReceivedError() 2/3 REQ HEADERS: %s", SPACE, request.getRequestHeaders())); 62 | logger.logError(String.format(LOCALE, "%s onReceivedError() 3/3 ERR DESC : %s", SPACE, error.getDescription())); 63 | } 64 | } 65 | 66 | public void onReceivedError(final WebView view, final int errorCode, final String description, final String failingUrl) { 67 | if (loggingEnabled) { 68 | logger.logError(String.format(LOCALE, "%s onReceivedError() 1/2 CALL: %d %s", SPACE, errorCode, failingUrl)); 69 | logger.logError(String.format(LOCALE, "%s onReceivedError() 2/2 ERR : %s", SPACE, description)); 70 | } 71 | } 72 | 73 | @RequiresApi(api = Build.VERSION_CODES.M) 74 | public void onReceivedHttpError(final WebView view, final WebResourceRequest request, final WebResourceResponse errorResponse) { 75 | if (loggingEnabled) { 76 | final Uri url = request.getUrl(); 77 | final int code = errorResponse.getStatusCode(); 78 | final String method = request.getMethod(); 79 | 80 | logger.logError(String.format(LOCALE, "%s onReceivedHttpError() 1/4 CALL : %d %s %s", SPACE, code, method, url)); 81 | logger.logError(String.format(LOCALE, "%s onReceivedHttpError() 2/4 REQ HEADERS: %s", SPACE, request.getRequestHeaders())); 82 | logger.logError(String.format(LOCALE, "%s onReceivedHttpError() 3/4 ERR DESC : %s", SPACE, errorResponse.getReasonPhrase())); 83 | logger.logError(String.format(LOCALE, "%s onReceivedHttpError() 4/4 ERR HEADERS: %s", SPACE, errorResponse.getResponseHeaders())); 84 | } 85 | } 86 | 87 | public void onReceivedSslError(final WebView view, final SslErrorHandler handler, final SslError error) { 88 | if (loggingEnabled) { 89 | logger.logError(String.format(LOCALE, "%s onReceivedSslError() ERR: %s", SPACE, error)); 90 | } 91 | } 92 | 93 | @RequiresApi(api = Build.VERSION_CODES.N) 94 | public void shouldOverrideUrlLoading(WebView view, WebResourceRequest request, boolean retVal) { 95 | if (loggingEnabled) { 96 | final Uri url = request.getUrl(); 97 | final String method = request.getMethod(); 98 | final boolean redirect = request.isRedirect(); 99 | final boolean mainframe = request.isForMainFrame(); 100 | final boolean gesture = request.hasGesture(); 101 | 102 | logger.log(String.format(LOCALE, "%s shouldOverrideUrlLoading() 1/4 CALL : %s %s", SPACE, method, url)); 103 | logger.log(String.format(LOCALE, "%s shouldOverrideUrlLoading() 2/4 CALL INFO : redirect=%s, forMainFrame=%s, hasGesture=%s", SPACE, redirect, mainframe, gesture)); 104 | logger.log(String.format(LOCALE, "%s shouldOverrideUrlLoading() 3/4 REQ HEADERS: %s", SPACE, request.getRequestHeaders())); 105 | logger.log(String.format(LOCALE, "%s shouldOverrideUrlLoading() 4/4 OVERRIDE : %s", SPACE, retVal)); 106 | } 107 | } 108 | 109 | public void shouldOverrideUrlLoading(WebView view, String url, boolean retVal) { 110 | if (loggingEnabled) { 111 | logger.log(String.format(LOCALE, "%s shouldOverrideUrlLoading() 1/2 CALL : %s", SPACE, url)); 112 | logger.log(String.format(LOCALE, "%s shouldOverrideUrlLoading() 2/2 OVERRIDE: %s", SPACE, retVal)); 113 | } 114 | } 115 | 116 | public void onLoadResource(WebView view, String url) { 117 | if (loggingEnabled) { 118 | logger.log(String.format(LOCALE, "%s onLoadResource() %s", SPACE, url)); 119 | } 120 | } 121 | 122 | @TargetApi(Build.VERSION_CODES.M) 123 | public void onPageCommitVisible(WebView view, String url) { 124 | if (loggingEnabled) { 125 | logger.log(String.format(LOCALE, "%s onPageCommitVisible() %s", SPACE, url)); 126 | } 127 | } 128 | 129 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 130 | public void shouldInterceptRequest(WebView view, String url, WebResourceResponse retVal) { 131 | if (loggingEnabled) { 132 | final String result = retVal == null ? "false" : StringUtils.toString(retVal); 133 | 134 | logger.log(String.format(LOCALE, "%s shouldInterceptRequest() 1/2 CALL : %s", SPACE, url)); 135 | logger.log(String.format(LOCALE, "%s shouldInterceptRequest() 2/2 OVERRIDE: %s", SPACE, url, result)); 136 | } 137 | } 138 | 139 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 140 | public void shouldInterceptRequest(WebView view, WebResourceRequest request, final WebResourceResponse retVal) { 141 | if (loggingEnabled) { 142 | final Uri url = request.getUrl(); 143 | final String method = request.getMethod(); 144 | final String result = retVal == null ? "false" : StringUtils.toString(retVal); 145 | 146 | logger.log(String.format(LOCALE, "%s shouldInterceptRequest() 1/3 CALL : %s %s", SPACE, method, url)); 147 | logger.log(String.format(LOCALE, "%s shouldInterceptRequest() 2/3 REQ HEADERS: %s", SPACE, request.getRequestHeaders())); 148 | logger.log(String.format(LOCALE, "%s shouldInterceptRequest() 3/3 INTERCEPT : %s", SPACE, result)); 149 | } 150 | } 151 | 152 | public void onTooManyRedirects(WebView view, Message cancelMsg, Message continueMsg) { 153 | if (loggingEnabled) { 154 | logger.logError(String.format(LOCALE, "%s onTooManyRedirects()", SPACE)); 155 | } 156 | } 157 | 158 | public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) { 159 | if (loggingEnabled) { 160 | logger.logSecurity(String.format(LOCALE, "%s onReceivedHttpAuthRequest() %s %s %s", SPACE, host, realm, handler)); 161 | } 162 | } 163 | 164 | public void onPageStarted(WebView view, String url, Bitmap facIcon) { 165 | if (loggingEnabled) { 166 | logger.log(String.format(LOCALE, "%s onPageStarted() %s", IN, url)); 167 | } 168 | } 169 | 170 | public void onPageFinished(WebView view, String url) { 171 | if (loggingEnabled) { 172 | logger.log(String.format(LOCALE, "%s onPageFinished() %s", OUT, url)); 173 | } 174 | } 175 | 176 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 177 | public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) { 178 | if (loggingEnabled) { 179 | logger.logSecurity(String.format(LOCALE, "%s onReceivedClientCertRequest() %s", SPACE, StringUtils.toString(request))); 180 | } 181 | } 182 | 183 | public void onFormResubmission(final WebView view, final Message dontResend, final Message resend) { 184 | if (loggingEnabled) { 185 | logger.log(String.format(LOCALE, "%s onFormResubmission()", SPACE)); 186 | } 187 | } 188 | 189 | public void doUpdateVisitedHistory(final WebView view, final String url, final boolean isReload) { 190 | if (loggingEnabled) { 191 | logger.log(String.format(LOCALE, "%s doUpdateVisitedHistory() %s, isReload: %s", SPACE, url, isReload)); 192 | } 193 | } 194 | 195 | public void shouldOverrideKeyEvent(final WebView view, final KeyEvent event, boolean retVal) { 196 | if (loggingEnabled && logKeyEventsEnabled) { 197 | logger.logKeyEvent(String.format(LOCALE, "%s shouldOverrideKeyEvent() 1/2 EVENT : %s", SPACE, event)); 198 | logger.logKeyEvent(String.format(LOCALE, "%s shouldOverrideKeyEvent() 2/2 OVERRIDE: %s", SPACE, retVal)); 199 | } 200 | } 201 | 202 | public void onUnhandledInputEvent(final WebView view, final InputEvent event) { 203 | if (loggingEnabled && logKeyEventsEnabled) { 204 | logger.logKeyEvent(String.format(LOCALE, "%s onUnhandledInputEvent() %s", SPACE, event)); 205 | } 206 | } 207 | 208 | public void onUnhandledKeyEvent(final WebView view, final KeyEvent event) { 209 | if (loggingEnabled && logKeyEventsEnabled) { 210 | logger.logKeyEvent(String.format(LOCALE, "%s onUnhandledKeyEvent() %s", SPACE, event)); 211 | } 212 | } 213 | 214 | public void onScaleChanged(final WebView view, final float oldScale, final float newScale) { 215 | if (loggingEnabled) { 216 | logger.log(String.format(LOCALE, "%s onScaleChanged() old: %s, new: %s", SPACE, oldScale, newScale)); 217 | } 218 | } 219 | 220 | public void onReceivedLoginRequest(final WebView view, final String realm, final String account, final String args) { 221 | if (loggingEnabled) { 222 | logger.logSecurity(String.format(LOCALE, "%s onReceivedLoginRequest() %s, %s, %s", SPACE, realm, account, args)); 223 | } 224 | } 225 | 226 | @RequiresApi(api = Build.VERSION_CODES.O) 227 | public void onRenderProcessGone(final WebView view, final RenderProcessGoneDetail detail, boolean retVal) { 228 | if (loggingEnabled) { 229 | logger.log(String.format(LOCALE, "%s onRenderProcessGone() 1/2 DETAIL: %s", SPACE, detail)); 230 | logger.log(String.format(LOCALE, "%s onRenderProcessGone() 2/2 RESULT: %s", SPACE, retVal)); 231 | } 232 | } 233 | 234 | @RequiresApi(api = Build.VERSION_CODES.O_MR1) 235 | public void onSafeBrowsingHit(final WebView view, final WebResourceRequest request, final int threatType, final SafeBrowsingResponse callback) { 236 | if (loggingEnabled) { 237 | final Uri url = request.getUrl(); 238 | final String method = request.getMethod(); 239 | final String threat = StringUtils.resolveThreatType(threatType); 240 | 241 | logger.log(String.format(LOCALE, "%s onSafeBrowsingHit() 1/3 CALL : %s %s", SPACE, method, url)); 242 | logger.log(String.format(LOCALE, "%s onSafeBrowsingHit() 2/3 REQ HEADERS: %s", SPACE, request.getRequestHeaders())); 243 | logger.log(String.format(LOCALE, "%s onSafeBrowsingHit() 3/3 THREAT : %s", SPACE, threat)); 244 | } 245 | } 246 | 247 | @Override 248 | public boolean isLoggingEnabled() { 249 | return loggingEnabled; 250 | } 251 | 252 | @Override 253 | public void setLoggingEnabled(final boolean enabled) { 254 | this.loggingEnabled = enabled; 255 | } 256 | 257 | @Override 258 | public boolean isLogKeyEventsEnabled() { 259 | return logKeyEventsEnabled; 260 | } 261 | 262 | @Override 263 | public void setLogKeyEventsEnabled(final boolean enabled) { 264 | this.logKeyEventsEnabled = enabled; 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /library/src/test/java/uk/co/alt236/webviewdebug/webviewclient/DebugWebViewClientLoggerTest.java: -------------------------------------------------------------------------------- 1 | package uk.co.alt236.webviewdebug.webviewclient; 2 | 3 | import android.graphics.Bitmap; 4 | import android.net.http.SslError; 5 | import android.os.Build; 6 | import android.os.Message; 7 | import android.view.InputEvent; 8 | import android.view.KeyEvent; 9 | import android.webkit.ClientCertRequest; 10 | import android.webkit.HttpAuthHandler; 11 | import android.webkit.RenderProcessGoneDetail; 12 | import android.webkit.SafeBrowsingResponse; 13 | import android.webkit.SslErrorHandler; 14 | import android.webkit.WebResourceError; 15 | import android.webkit.WebResourceRequest; 16 | import android.webkit.WebResourceResponse; 17 | import android.webkit.WebView; 18 | 19 | import org.junit.After; 20 | import org.junit.Before; 21 | import org.junit.Test; 22 | import org.junit.runner.RunWith; 23 | import org.mockito.Mockito; 24 | import org.robolectric.RobolectricTestRunner; 25 | import org.robolectric.annotation.Config; 26 | 27 | import static junit.framework.Assert.assertFalse; 28 | import static junit.framework.Assert.assertTrue; 29 | 30 | @RunWith(RobolectricTestRunner.class) 31 | @Config(manifest = Config.NONE, sdk = Build.VERSION_CODES.O) 32 | public class DebugWebViewClientLoggerTest { 33 | 34 | private WebView webView; 35 | private LogEngine logEngine; 36 | private DebugWebViewClientLogger logger; 37 | 38 | @Before 39 | public void setUp() { 40 | webView = Mockito.mock(WebView.class); 41 | logEngine = Mockito.mock(LogEngine.class); 42 | logger = new DebugWebViewClientLogger(logEngine); 43 | } 44 | 45 | @After 46 | public void tearDown() { 47 | webView = null; 48 | logEngine = null; 49 | logger = null; 50 | } 51 | 52 | @Test 53 | public void onReceivedError() { 54 | final int code = 500; 55 | final String message = "foo"; 56 | final String url = "bar"; 57 | 58 | logger.setLoggingEnabled(false); 59 | logger.onReceivedError(webView, code, message, url); 60 | verifyLogNotCalled(); 61 | 62 | logger.setLoggingEnabled(true); 63 | logger.onReceivedError(webView, code, message, url); 64 | verifyLogEngine().logError(Mockito.anyString()); 65 | } 66 | 67 | @Test 68 | public void onReceivedError_api23() { 69 | final WebResourceRequest request = Mockito.mock(WebResourceRequest.class); 70 | final WebResourceError error = Mockito.mock(WebResourceError.class); 71 | 72 | logger.setLoggingEnabled(false); 73 | logger.onReceivedError(webView, request, error); 74 | verifyLogNotCalled(); 75 | 76 | logger.setLoggingEnabled(true); 77 | logger.onReceivedError(webView, request, error); 78 | verifyLogEngine().logError(Mockito.anyString()); 79 | } 80 | 81 | @Test 82 | public void onReceivedHttpError() { 83 | final WebResourceRequest request = Mockito.mock(WebResourceRequest.class); 84 | final WebResourceResponse response = Mockito.mock(WebResourceResponse.class); 85 | 86 | logger.setLoggingEnabled(false); 87 | logger.onReceivedHttpError(webView, request, response); 88 | verifyLogNotCalled(); 89 | 90 | logger.setLoggingEnabled(true); 91 | logger.onReceivedHttpError(webView, request, response); 92 | verifyLogEngine().logError(Mockito.anyString()); 93 | } 94 | 95 | @Test 96 | public void onReceivedSslError() { 97 | final SslErrorHandler errorHandler = Mockito.mock(SslErrorHandler.class); 98 | final SslError sslError = Mockito.mock(SslError.class); 99 | 100 | logger.setLoggingEnabled(false); 101 | logger.onReceivedSslError(webView, errorHandler, sslError); 102 | verifyLogNotCalled(); 103 | 104 | logger.setLoggingEnabled(true); 105 | logger.onReceivedSslError(webView, errorHandler, sslError); 106 | verifyLogEngine().logError(Mockito.anyString()); 107 | } 108 | 109 | @Test 110 | public void shouldOverrideUrlLoading() { 111 | final String url = "foo"; 112 | 113 | logger.setLoggingEnabled(false); 114 | logger.shouldOverrideUrlLoading(webView, url, false); 115 | verifyLogNotCalled(); 116 | 117 | logger.setLoggingEnabled(true); 118 | logger.shouldOverrideUrlLoading(webView, url, false); 119 | verifyLogEngine().log(Mockito.anyString()); 120 | } 121 | 122 | @Test 123 | public void shouldOverrideUrlLoading1() { 124 | final WebResourceRequest request = Mockito.mock(WebResourceRequest.class); 125 | 126 | logger.setLoggingEnabled(false); 127 | logger.shouldOverrideUrlLoading(webView, request, false); 128 | verifyLogNotCalled(); 129 | 130 | logger.setLoggingEnabled(true); 131 | logger.shouldOverrideUrlLoading(webView, request, false); 132 | verifyLogEngine().log(Mockito.anyString()); 133 | } 134 | 135 | @Test 136 | public void onLoadResource() { 137 | final String url = "foo"; 138 | 139 | logger.setLoggingEnabled(false); 140 | logger.onLoadResource(webView, url); 141 | verifyLogNotCalled(); 142 | 143 | logger.setLoggingEnabled(true); 144 | logger.onLoadResource(webView, url); 145 | verifyLogEngine().log(Mockito.anyString()); 146 | } 147 | 148 | @Test 149 | public void onPageCommitVisible() { 150 | final String url = "foo"; 151 | 152 | logger.setLoggingEnabled(false); 153 | logger.onPageCommitVisible(webView, url); 154 | verifyLogNotCalled(); 155 | 156 | logger.setLoggingEnabled(true); 157 | logger.onPageCommitVisible(webView, url); 158 | verifyLogEngine().log(Mockito.anyString()); 159 | } 160 | 161 | @Test 162 | public void shouldInterceptRequest() { 163 | final String url = "foo"; 164 | final WebResourceResponse response = Mockito.mock(WebResourceResponse.class); 165 | 166 | logger.setLoggingEnabled(false); 167 | logger.shouldInterceptRequest(webView, url, response); 168 | verifyLogNotCalled(); 169 | 170 | logger.setLoggingEnabled(true); 171 | logger.shouldInterceptRequest(webView, url, response); 172 | verifyLogEngine().log(Mockito.anyString()); 173 | } 174 | 175 | @Test 176 | public void shouldInterceptRequest_api21() { 177 | final WebResourceRequest request = Mockito.mock(WebResourceRequest.class); 178 | final WebResourceResponse response = Mockito.mock(WebResourceResponse.class); 179 | 180 | logger.setLoggingEnabled(false); 181 | logger.shouldInterceptRequest(webView, request, response); 182 | verifyLogNotCalled(); 183 | 184 | logger.setLoggingEnabled(true); 185 | logger.shouldInterceptRequest(webView, request, response); 186 | verifyLogEngine().log(Mockito.anyString()); 187 | } 188 | 189 | @Test 190 | public void onTooManyRedirects() { 191 | final Message cancelMsg = Mockito.mock(Message.class); 192 | final Message continueMsg = Mockito.mock(Message.class); 193 | 194 | logger.setLoggingEnabled(false); 195 | logger.onTooManyRedirects(webView, cancelMsg, continueMsg); 196 | verifyLogNotCalled(); 197 | 198 | logger.setLoggingEnabled(true); 199 | logger.onTooManyRedirects(webView, cancelMsg, continueMsg); 200 | verifyLogEngine().logError(Mockito.anyString()); 201 | } 202 | 203 | @Test 204 | public void onReceivedHttpAuthRequest() { 205 | final HttpAuthHandler handler = Mockito.mock(HttpAuthHandler.class); 206 | final String host = "foo"; 207 | final String realm = "bar"; 208 | 209 | logger.setLoggingEnabled(false); 210 | logger.onReceivedHttpAuthRequest(webView, handler, host, realm); 211 | verifyLogNotCalled(); 212 | 213 | logger.setLoggingEnabled(true); 214 | logger.onReceivedHttpAuthRequest(webView, handler, host, realm); 215 | verifyLogEngine().logSecurity(Mockito.anyString()); 216 | } 217 | 218 | @Test 219 | public void onPageStarted() { 220 | final String url = "foo"; 221 | final Bitmap bitmap = Mockito.mock(Bitmap.class); 222 | 223 | logger.setLoggingEnabled(false); 224 | logger.onPageStarted(webView, url, bitmap); 225 | verifyLogNotCalled(); 226 | 227 | logger.setLoggingEnabled(true); 228 | logger.onPageStarted(webView, url, bitmap); 229 | verifyLogEngine().log(Mockito.anyString()); 230 | } 231 | 232 | @Test 233 | public void onPageFinished() { 234 | final String url = "foo"; 235 | 236 | logger.setLoggingEnabled(false); 237 | logger.onPageFinished(webView, url); 238 | verifyLogNotCalled(); 239 | 240 | logger.setLoggingEnabled(true); 241 | logger.onPageFinished(webView, url); 242 | verifyLogEngine().log(Mockito.anyString()); 243 | } 244 | 245 | @Test 246 | public void onReceivedClientCertRequest() { 247 | final ClientCertRequest request = Mockito.mock(ClientCertRequest.class); 248 | 249 | logger.setLoggingEnabled(false); 250 | logger.onReceivedClientCertRequest(webView, request); 251 | verifyLogNotCalled(); 252 | 253 | logger.setLoggingEnabled(true); 254 | logger.onReceivedClientCertRequest(webView, request); 255 | verifyLogEngine().logSecurity(Mockito.anyString()); 256 | } 257 | 258 | @Test 259 | public void onFormResubmission() { 260 | final Message dontResend = Mockito.mock(Message.class); 261 | final Message resend = Mockito.mock(Message.class); 262 | 263 | logger.setLoggingEnabled(false); 264 | logger.onFormResubmission(webView, dontResend, resend); 265 | verifyLogNotCalled(); 266 | 267 | logger.setLoggingEnabled(true); 268 | logger.onFormResubmission(webView, dontResend, resend); 269 | verifyLogEngine().log(Mockito.anyString()); 270 | } 271 | 272 | @Test 273 | public void doUpdateVisitedHistory() { 274 | final String url = "foo"; 275 | final boolean reload = true; 276 | 277 | logger.setLoggingEnabled(false); 278 | logger.doUpdateVisitedHistory(webView, url, reload); 279 | verifyLogNotCalled(); 280 | 281 | logger.setLoggingEnabled(true); 282 | logger.doUpdateVisitedHistory(webView, url, reload); 283 | verifyLogEngine().log(Mockito.anyString()); 284 | } 285 | 286 | @Test 287 | public void shouldOverrideKeyEvent() { 288 | final KeyEvent keyEvent = Mockito.mock(KeyEvent.class); 289 | 290 | logger.setLoggingEnabled(false); 291 | logger.setLogKeyEventsEnabled(false); 292 | logger.shouldOverrideKeyEvent(webView, keyEvent, false); 293 | verifyLogNotCalled(); 294 | 295 | logger.setLoggingEnabled(false); 296 | logger.setLogKeyEventsEnabled(true); 297 | logger.shouldOverrideKeyEvent(webView, keyEvent, false); 298 | verifyLogNotCalled(); 299 | 300 | logger.setLoggingEnabled(true); 301 | logger.setLogKeyEventsEnabled(false); 302 | logger.shouldOverrideKeyEvent(webView, keyEvent, false); 303 | verifyLogNotCalled(); 304 | 305 | logger.setLoggingEnabled(true); 306 | logger.setLogKeyEventsEnabled(true); 307 | logger.shouldOverrideKeyEvent(webView, keyEvent, false); 308 | verifyLogEngine().logKeyEvent(Mockito.anyString()); 309 | } 310 | 311 | @Test 312 | public void onUnhandledInputEvent() { 313 | final InputEvent inputEvent = Mockito.mock(InputEvent.class); 314 | 315 | logger.setLoggingEnabled(false); 316 | logger.setLogKeyEventsEnabled(false); 317 | logger.onUnhandledInputEvent(webView, inputEvent); 318 | verifyLogNotCalled(); 319 | 320 | logger.setLoggingEnabled(false); 321 | logger.setLogKeyEventsEnabled(true); 322 | logger.onUnhandledInputEvent(webView, inputEvent); 323 | verifyLogNotCalled(); 324 | 325 | logger.setLoggingEnabled(true); 326 | logger.setLogKeyEventsEnabled(false); 327 | logger.onUnhandledInputEvent(webView, inputEvent); 328 | verifyLogNotCalled(); 329 | 330 | logger.setLoggingEnabled(true); 331 | logger.setLogKeyEventsEnabled(true); 332 | logger.onUnhandledInputEvent(webView, inputEvent); 333 | verifyLogEngine().logKeyEvent(Mockito.anyString()); 334 | } 335 | 336 | @Test 337 | public void onUnhandledKeyEvent() { 338 | final KeyEvent keyEvent = Mockito.mock(KeyEvent.class); 339 | 340 | logger.setLoggingEnabled(false); 341 | logger.setLogKeyEventsEnabled(false); 342 | logger.onUnhandledKeyEvent(webView, keyEvent); 343 | verifyLogNotCalled(); 344 | 345 | logger.setLoggingEnabled(false); 346 | logger.setLogKeyEventsEnabled(true); 347 | logger.onUnhandledKeyEvent(webView, keyEvent); 348 | verifyLogNotCalled(); 349 | 350 | logger.setLoggingEnabled(true); 351 | logger.setLogKeyEventsEnabled(false); 352 | logger.onUnhandledKeyEvent(webView, keyEvent); 353 | verifyLogNotCalled(); 354 | 355 | logger.setLoggingEnabled(true); 356 | logger.setLogKeyEventsEnabled(true); 357 | logger.onUnhandledKeyEvent(webView, keyEvent); 358 | verifyLogEngine().logKeyEvent(Mockito.anyString()); 359 | } 360 | 361 | @Test 362 | public void onScaleChanged() { 363 | final float oldScale = 1.0f; 364 | final float newScale = 2.0f; 365 | 366 | logger.setLoggingEnabled(false); 367 | logger.onScaleChanged(webView, oldScale, newScale); 368 | verifyLogNotCalled(); 369 | 370 | logger.setLoggingEnabled(true); 371 | logger.onScaleChanged(webView, oldScale, newScale); 372 | verifyLogEngine().log(Mockito.anyString()); 373 | } 374 | 375 | @Test 376 | public void onReceivedLoginRequest() { 377 | final String realm = "realm"; 378 | final String account = "account"; 379 | final String args = "args"; 380 | 381 | logger.setLoggingEnabled(false); 382 | logger.onReceivedLoginRequest(webView, realm, account, args); 383 | verifyLogNotCalled(); 384 | 385 | logger.setLoggingEnabled(true); 386 | logger.onReceivedLoginRequest(webView, realm, account, args); 387 | verifyLogEngine().logSecurity(Mockito.anyString()); 388 | } 389 | 390 | @Test 391 | public void onRenderProcessGone() { 392 | final RenderProcessGoneDetail detail = Mockito.mock(RenderProcessGoneDetail.class); 393 | 394 | logger.setLoggingEnabled(false); 395 | logger.onRenderProcessGone(webView, detail, false); 396 | verifyLogNotCalled(); 397 | 398 | logger.setLoggingEnabled(true); 399 | logger.onRenderProcessGone(webView, detail, false); 400 | verifyLogEngine().log(Mockito.anyString()); 401 | } 402 | 403 | @Test 404 | public void onSafeBrowsingHit() { 405 | final WebResourceRequest request = Mockito.mock(WebResourceRequest.class); 406 | final SafeBrowsingResponse callback = Mockito.mock(SafeBrowsingResponse.class); 407 | final int threatType = -1; 408 | 409 | logger.setLoggingEnabled(false); 410 | logger.onSafeBrowsingHit(webView, request, threatType, callback); 411 | verifyLogNotCalled(); 412 | 413 | logger.setLoggingEnabled(true); 414 | logger.onSafeBrowsingHit(webView, request, threatType, callback); 415 | verifyLogEngine().log(Mockito.anyString()); 416 | } 417 | 418 | @Test 419 | public void testGettersAndSetters() { 420 | assertFalse(logger.isLoggingEnabled()); 421 | logger.setLoggingEnabled(true); 422 | assertTrue(logger.isLoggingEnabled()); 423 | 424 | assertFalse(logger.isLogKeyEventsEnabled()); 425 | logger.setLogKeyEventsEnabled(true); 426 | assertTrue(logger.isLogKeyEventsEnabled()); 427 | } 428 | 429 | private void verifyLogNotCalled() { 430 | Mockito.verify(logEngine, Mockito.never()).log(Mockito.anyString()); 431 | Mockito.verify(logEngine, Mockito.never()).logError(Mockito.anyString()); 432 | Mockito.verify(logEngine, Mockito.never()).logSecurity(Mockito.anyString()); 433 | Mockito.verify(logEngine, Mockito.never()).logKeyEvent(Mockito.anyString()); 434 | } 435 | 436 | private LogEngine verifyLogEngine() { 437 | return Mockito.verify(logEngine, Mockito.atLeastOnce()); 438 | } 439 | } 440 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WEBVIEW DEBUG 2 | []( https://android-arsenal.com/details/3/6436 ) 3 | 4 | Provides a logging wrapper around a WebViewClient, in order to figure out what is going on. 5 | 6 | This happens by creating a `DebugWebViewClient` which logs events and passes them to an enclosed `WebViewClient`. 7 | 8 | ## Warning 9 | The `DebugWebViewClient` is implementing all `WebViewClient` up to API 26. If your `WebViewClient` is implementing a method that the `DebugWebViewClient` does not, and that method is critical for your business logic, then your app will probably not work properly. 10 | 11 | When a `DebugWebViewClient` is initialised, it will print in log a list of all methods that are declared in the passed `WebViewClient` class and any parents and are NOT overridden. 12 | 13 | For as long as your app is does not need any of the listed, non-overridden, methods, then there won't be a problem. 14 | 15 | ## Getting the Library 16 | 17 | gradle 18 | ```groovy 19 | repositories { 20 | maven { 21 | url "https://dl.bintray.com/alt236/maven" 22 | } 23 | } 24 | 25 | dependencies { 26 | compile 'uk.co.alt236:webviewdebug:1.0.0' 27 | } 28 | ``` 29 | 30 | ## Usage 31 | 32 | Output in logcat uses this tag: `DebugWVClient`. 33 | 34 | ### Debugging a WebViewClient 35 | ###### 1. Fast way if you already have a WebViewClient 36 | If you already have a `WebViewClient` implementation, wrap it with `DebugWebViewClient` before assigning it to the WebView. 37 | 38 | ```java 39 | final DebugWebViewClient debugWebViewClient = new DebugWebViewClient(new MyCustomWebViewClient()); 40 | debugWebViewClient.setLoggingEnabled(true); 41 | webView.setWebViewClient(debugWebViewClient); 42 | ``` 43 | 44 | ###### 2. You have a custom WebViewClient but want more control 45 | You can use `DebugWebViewClientLogger` to log things as needed in your own `WebViewClient`. 46 | 47 | Make sure you pass the parameters and any return values of your own `WebViewClient` to the equivalent methods of the `DebugWebViewClientLogger`. 48 | 49 | ###### 3. You don't have a WebViewClient but you want to know what is going on 50 | Just instantiate and assign a `DebugWebViewClient`to the WebView. 51 | 52 | ```java 53 | final DebugWebViewClient debugWebViewClient = new DebugWebViewClient(); 54 | debugWebViewClient.setLoggingEnabled(true); 55 | webView.setWebViewClient(debugWebViewClient); 56 | ``` 57 | 58 | ### Controlling output 59 | Both `DebugWebViewClient` and `DebugWebViewClientLogger` implemetn `LogControl` which contains the following signatures: 60 | 61 | * `isLoggingEnabled()`: Check if logging is globally enabled 62 | * `setLoggingEnabled(boolean)`: Enable or disable logging 63 | * `isLogKeyEventsEnabled()`: Check if logging of `KeyEvent` related methods is enabled 64 | * `setLogKeyEventsEnabled(boolean)`: Enable or disable logging of `KeyEvent` related methods is enabled 65 | 66 | `KeyEvent` related methods have more granularity due to privacy concerns, as all keystrokes will be logged. 67 | 68 | `setLoggingEnabled(boolean)` is a global switch which overrides `setLogKeyEventsEnabled(boolean)` 69 | 70 | ## Sample output 71 | ``` 72 | D/DebugWVClient: All methods implemented :) 73 | I/DebugWVClient: ---> onPageStarted() http://www.google.com/ 74 | I/DebugWVClient: shouldInterceptRequest() 1/3 CALL : GET http://www.google.com/ 75 | I/DebugWVClient: shouldInterceptRequest() 2/3 REQ HEADERS: {User-Agent=Mozilla/5.0 (Linux; Android 8.0.0; Android SDK built for x86 Build/OSR1.170720.005; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.125 Mobile Safari/537.36, Accept=text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8, Upgrade-Insecure-Requests=1} 76 | I/DebugWVClient: shouldInterceptRequest() 3/3 INTERCEPT : false 77 | I/DebugWVClient: onLoadResource() http://www.google.com/ 78 | I/DebugWVClient: ---> onPageStarted() http://www.google.co.uk/?gfe_rd=cr&dcr=0&ei=sGrWWdSvNq_38AfK37roAg 79 | I/DebugWVClient: shouldOverrideUrlLoading() 1/4 CALL : GET http://www.google.co.uk/?gfe_rd=cr&dcr=0&ei=sGrWWdSvNq_38AfK37roAg 80 | I/DebugWVClient: shouldOverrideUrlLoading() 2/4 CALL INFO : redirect=true, forMainFrame=true, hasGesture=false 81 | I/DebugWVClient: shouldOverrideUrlLoading() 3/4 REQ HEADERS: null 82 | I/DebugWVClient: shouldOverrideUrlLoading() 4/4 OVERRIDE : false 83 | I/DebugWVClient: ---> onPageStarted() https://www.google.co.uk/?gfe_rd=cr&dcr=0&ei=sGrWWdSvNq_38AfK37roAg&gws_rd=ssl 84 | I/DebugWVClient: shouldOverrideUrlLoading() 1/4 CALL : GET https://www.google.co.uk/?gfe_rd=cr&dcr=0&ei=sGrWWdSvNq_38AfK37roAg&gws_rd=ssl 85 | I/DebugWVClient: shouldOverrideUrlLoading() 2/4 CALL INFO : redirect=true, forMainFrame=true, hasGesture=false 86 | I/DebugWVClient: shouldOverrideUrlLoading() 3/4 REQ HEADERS: null 87 | I/DebugWVClient: shouldOverrideUrlLoading() 4/4 OVERRIDE : false 88 | I/DebugWVClient: doUpdateVisitedHistory() https://www.google.co.uk/?gfe_rd=cr&dcr=0&ei=sGrWWdSvNq_38AfK37roAg&gws_rd=ssl, isReload: false 89 | I/DebugWVClient: shouldInterceptRequest() 1/3 CALL : GET https://www.google.co.uk/images/hpp/gsa_super_g-64.gif 90 | I/DebugWVClient: shouldInterceptRequest() 2/3 REQ HEADERS: {User-Agent=Mozilla/5.0 (Linux; Android 8.0.0; Android SDK built for x86 Build/OSR1.170720.005; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.125 Mobile Safari/537.36, Accept=image/webp,image/*,*/*;q=0.8, Referer=https://www.google.co.uk/?gfe_rd=cr&dcr=0&ei=sGrWWdSvNq_38AfK37roAg&gws_rd=ssl} 91 | I/DebugWVClient: shouldInterceptRequest() 3/3 INTERCEPT : false 92 | I/DebugWVClient: onLoadResource() https://www.google.co.uk/images/hpp/gsa_super_g-64.gif 93 | I/DebugWVClient: shouldInterceptRequest() 1/3 CALL : GET https://www.google.co.uk/images/branding/googlelogo/2x/googlelogo_color_160x56dp.png 94 | I/DebugWVClient: shouldInterceptRequest() 2/3 REQ HEADERS: {User-Agent=Mozilla/5.0 (Linux; Android 8.0.0; Android SDK built for x86 Build/OSR1.170720.005; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.125 Mobile Safari/537.36, Accept=image/webp,image/*,*/*;q=0.8, Referer=https://www.google.co.uk/?gfe_rd=cr&dcr=0&ei=sGrWWdSvNq_38AfK37roAg&gws_rd=ssl} 95 | I/DebugWVClient: shouldInterceptRequest() 3/3 INTERCEPT : false 96 | I/DebugWVClient: onLoadResource() https://www.google.co.uk/images/branding/googlelogo/2x/googlelogo_color_160x56dp.png 97 | I/DebugWVClient: shouldInterceptRequest() 1/3 CALL : GET https://ssl.gstatic.com/gb/images/qi2_00ed8ca1.png 98 | I/DebugWVClient: shouldInterceptRequest() 2/3 REQ HEADERS: {User-Agent=Mozilla/5.0 (Linux; Android 8.0.0; Android SDK built for x86 Build/OSR1.170720.005; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.125 Mobile Safari/537.36, Accept=image/webp,image/*,*/*;q=0.8, Referer=https://www.google.co.uk/?gfe_rd=cr&dcr=0&ei=sGrWWdSvNq_38AfK37roAg&gws_rd=ssl} 99 | I/DebugWVClient: shouldInterceptRequest() 3/3 INTERCEPT : false 100 | I/DebugWVClient: onLoadResource() https://ssl.gstatic.com/gb/images/qi2_00ed8ca1.png 101 | I/DebugWVClient: onPageCommitVisible() https://www.google.co.uk/?gfe_rd=cr&dcr=0&ei=sGrWWdSvNq_38AfK37roAg&gws_rd=ssl 102 | I/DebugWVClient: shouldInterceptRequest() 1/3 CALL : GET https://www.google.co.uk/images/nav_logo242_hr.webp 103 | I/DebugWVClient: shouldInterceptRequest() 2/3 REQ HEADERS: {User-Agent=Mozilla/5.0 (Linux; Android 8.0.0; Android SDK built for x86 Build/OSR1.170720.005; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.125 Mobile Safari/537.36, Accept=image/webp,image/*,*/*;q=0.8, Referer=https://www.google.co.uk/?gfe_rd=cr&dcr=0&ei=sGrWWdSvNq_38AfK37roAg&gws_rd=ssl} 104 | I/DebugWVClient: shouldInterceptRequest() 3/3 INTERCEPT : false 105 | I/DebugWVClient: onLoadResource() https://www.google.co.uk/images/nav_logo242_hr.webp 106 | I/DebugWVClient: shouldInterceptRequest() 1/3 CALL : GET https://www.google.co.uk/images/branding/product/1x/gsa_android_144dp.png 107 | I/DebugWVClient: shouldInterceptRequest() 2/3 REQ HEADERS: {User-Agent=Mozilla/5.0 (Linux; Android 8.0.0; Android SDK built for x86 Build/OSR1.170720.005; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.125 Mobile Safari/537.36, Accept=image/webp,image/*,*/*;q=0.8, Referer=https://www.google.co.uk/?gfe_rd=cr&dcr=0&ei=sGrWWdSvNq_38AfK37roAg&gws_rd=ssl} 108 | I/DebugWVClient: shouldInterceptRequest() 3/3 INTERCEPT : false 109 | I/DebugWVClient: <--- onPageFinished() https://www.google.co.uk/?gfe_rd=cr&dcr=0&ei=sGrWWdSvNq_38AfK37roAg&gws_rd=ssl 110 | I/DebugWVClient: onLoadResource() https://www.google.co.uk/images/branding/product/1x/gsa_android_144dp.png 111 | I/DebugWVClient: All methods implemented :) 112 | I/DebugWVClient: shouldInterceptRequest() 1/3 CALL : GET http://www.google.com/ 113 | I/DebugWVClient: shouldInterceptRequest() 2/3 REQ HEADERS: {User-Agent=Mozilla/5.0 (Linux; Android 8.0.0; Android SDK built for x86 Build/OSR1.170720.005; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.125 Mobile Safari/537.36, Accept=text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8, Upgrade-Insecure-Requests=1} 114 | I/DebugWVClient: shouldInterceptRequest() 3/3 INTERCEPT : false 115 | I/DebugWVClient: ---> onPageStarted() http://www.google.com/ 116 | I/DebugWVClient: onLoadResource() http://www.google.com/ 117 | I/DebugWVClient: ---> onPageStarted() http://www.google.co.uk/?gfe_rd=cr&dcr=0&ei=AGvWWePPO6_38AfK37roAg 118 | I/DebugWVClient: shouldOverrideUrlLoading() 1/4 CALL : GET http://www.google.co.uk/?gfe_rd=cr&dcr=0&ei=AGvWWePPO6_38AfK37roAg 119 | I/DebugWVClient: shouldOverrideUrlLoading() 2/4 CALL INFO : redirect=true, forMainFrame=true, hasGesture=false 120 | I/DebugWVClient: shouldOverrideUrlLoading() 3/4 REQ HEADERS: null 121 | I/DebugWVClient: shouldOverrideUrlLoading() 4/4 OVERRIDE : false 122 | I/DebugWVClient: ---> onPageStarted() https://www.google.co.uk/?gfe_rd=cr&dcr=0&ei=AGvWWePPO6_38AfK37roAg&gws_rd=ssl 123 | I/DebugWVClient: shouldOverrideUrlLoading() 1/4 CALL : GET https://www.google.co.uk/?gfe_rd=cr&dcr=0&ei=AGvWWePPO6_38AfK37roAg&gws_rd=ssl 124 | I/DebugWVClient: shouldOverrideUrlLoading() 2/4 CALL INFO : redirect=true, forMainFrame=true, hasGesture=false 125 | I/DebugWVClient: shouldOverrideUrlLoading() 3/4 REQ HEADERS: null 126 | I/DebugWVClient: shouldOverrideUrlLoading() 4/4 OVERRIDE : false 127 | I/DebugWVClient: doUpdateVisitedHistory() https://www.google.co.uk/?gfe_rd=cr&dcr=0&ei=AGvWWePPO6_38AfK37roAg&gws_rd=ssl, isReload: false 128 | I/DebugWVClient: onPageCommitVisible() https://www.google.co.uk/?gfe_rd=cr&dcr=0&ei=AGvWWePPO6_38AfK37roAg&gws_rd=ssl 129 | I/DebugWVClient: shouldInterceptRequest() 1/3 CALL : GET https://www.google.co.uk/images/branding/product/1x/gsa_android_144dp.png 130 | I/DebugWVClient: shouldInterceptRequest() 2/3 REQ HEADERS: {User-Agent=Mozilla/5.0 (Linux; Android 8.0.0; Android SDK built for x86 Build/OSR1.170720.005; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.125 Mobile Safari/537.36, Accept=image/webp,image/*,*/*;q=0.8, Referer=https://www.google.co.uk/?gfe_rd=cr&dcr=0&ei=AGvWWePPO6_38AfK37roAg&gws_rd=ssl} 131 | I/DebugWVClient: shouldInterceptRequest() 3/3 INTERCEPT : false 132 | I/DebugWVClient: <--- onPageFinished() https://www.google.co.uk/?gfe_rd=cr&dcr=0&ei=AGvWWePPO6_38AfK37roAg&gws_rd=ssl 133 | I/DebugWVClient: onLoadResource() https://www.google.co.uk/images/branding/product/1x/gsa_android_144dp.png 134 | I/DebugWVClient: shouldOverrideUrlLoading() 1/4 CALL : GET https://www.google.com/url?q=https://store.google.com/gb/%3Fhl%3Den-GB%26countryRedirect%3Dtrue%26utm_source%3Dhpp%26utm_medium%3Dgoogle_oo%26utm_campaign%3DGS100077&source=hpp&id=19003843&ct=3&usg=AFQjCNGBVJRo84BJtfajQmUdhNeb8iYuSQ&sa=X&ved=0ahUKEwjbxISSgNrWAhUKJ8AKHetwDJ0Q8IcBCAk 135 | I/DebugWVClient: shouldOverrideUrlLoading() 2/4 CALL INFO : redirect=false, forMainFrame=true, hasGesture=true 136 | I/DebugWVClient: shouldOverrideUrlLoading() 3/4 REQ HEADERS: null 137 | I/DebugWVClient: shouldOverrideUrlLoading() 4/4 OVERRIDE : false 138 | I/DebugWVClient: shouldInterceptRequest() 1/3 CALL : GET https://www.google.com/url?q=https://store.google.com/gb/%3Fhl%3Den-GB%26countryRedirect%3Dtrue%26utm_source%3Dhpp%26utm_medium%3Dgoogle_oo%26utm_campaign%3DGS100077&source=hpp&id=19003843&ct=3&usg=AFQjCNGBVJRo84BJtfajQmUdhNeb8iYuSQ&sa=X&ved=0ahUKEwjbxISSgNrWAhUKJ8AKHetwDJ0Q8IcBCAk 139 | I/DebugWVClient: shouldInterceptRequest() 2/3 REQ HEADERS: {User-Agent=Mozilla/5.0 (Linux; Android 8.0.0; Android SDK built for x86 Build/OSR1.170720.005; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.125 Mobile Safari/537.36, Referer=https://www.google.co.uk/?gfe_rd=cr&dcr=0&ei=AGvWWePPO6_38AfK37roAg&gws_rd=ssl, Accept=text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8, Upgrade-Insecure-Requests=1} 140 | I/DebugWVClient: shouldInterceptRequest() 3/3 INTERCEPT : false 141 | I/DebugWVClient: ---> onPageStarted() https://www.google.com/url?q=https://store.google.com/gb/%3Fhl%3Den-GB%26countryRedirect%3Dtrue%26utm_source%3Dhpp%26utm_medium%3Dgoogle_oo%26utm_campaign%3DGS100077&source=hpp&id=19003843&ct=3&usg=AFQjCNGBVJRo84BJtfajQmUdhNeb8iYuSQ&sa=X&ved=0ahUKEwjbxISSgNrWAhUKJ8AKHetwDJ0Q8IcBCAk 142 | I/DebugWVClient: onLoadResource() https://www.google.com/url?q=https://store.google.com/gb/%3Fhl%3Den-GB%26countryRedirect%3Dtrue%26utm_source%3Dhpp%26utm_medium%3Dgoogle_oo%26utm_campaign%3DGS100077&source=hpp&id=19003843&ct=3&usg=AFQjCNGBVJRo84BJtfajQmUdhNeb8iYuSQ&sa=X&ved=0ahUKEwjbxISSgNrWAhUKJ8AKHetwDJ0Q8IcBCAk 143 | I/DebugWVClient: ---> onPageStarted() https://store.google.com/gb/?hl=en-GB&countryRedirect=true&utm_source=hpp&utm_medium=google_oo&utm_campaign=GS100077 144 | I/DebugWVClient: shouldOverrideUrlLoading() 1/4 CALL : GET https://store.google.com/gb/?hl=en-GB&countryRedirect=true&utm_source=hpp&utm_medium=google_oo&utm_campaign=GS100077 145 | I/DebugWVClient: shouldOverrideUrlLoading() 2/4 CALL INFO : redirect=true, forMainFrame=true, hasGesture=false 146 | I/DebugWVClient: shouldOverrideUrlLoading() 3/4 REQ HEADERS: null 147 | I/DebugWVClient: shouldOverrideUrlLoading() 4/4 OVERRIDE : false 148 | I/DebugWVClient: doUpdateVisitedHistory() https://store.google.com/gb/?hl=en-GB&countryRedirect=true&utm_source=hpp&utm_medium=google_oo&utm_campaign=GS100077, isReload: false 149 | I/DebugWVClient: shouldInterceptRequest() 1/3 CALL : GET https://www.googletagmanager.com/ns.html?id=GTM-MX89MJ 150 | I/DebugWVClient: shouldInterceptRequest() 2/3 REQ HEADERS: {User-Agent=Mozilla/5.0 (Linux; Android 8.0.0; Android SDK built for x86 Build/OSR1.170720.005; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.125 Mobile Safari/537.36, Referer=https://store.google.com/gb/?hl=en-GB&countryRedirect=true&utm_source=hpp&utm_medium=google_oo&utm_campaign=GS100077, Accept=text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8, Upgrade-Insecure-Requests=1} 151 | I/DebugWVClient: shouldInterceptRequest() 3/3 INTERCEPT : false 152 | I/DebugWVClient: onLoadResource() https://www.googletagmanager.com/ns.html?id=GTM-MX89MJ 153 | I/DebugWVClient: shouldInterceptRequest() 1/3 CALL : GET https://fonts.gstatic.com/s/productsans/v9/HYvgU2fE2nRJvZ5JFAumwRampu5_7CjHW5spxoeN3Vs.woff2 154 | I/DebugWVClient: shouldInterceptRequest() 2/3 REQ HEADERS: {User-Agent=Mozilla/5.0 (Linux; Android 8.0.0; Android SDK built for x86 Build/OSR1.170720.005; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.125 Mobile Safari/537.36, Origin=https://store.google.com, Referer=https://store.google.com/gb/?hl=en-GB&countryRedirect=true&utm_source=hpp&utm_medium=google_oo&utm_campaign=GS100077, Accept=*/*} 155 | I/DebugWVClient: shouldInterceptRequest() 3/3 INTERCEPT : false 156 | I/DebugWVClient: onLoadResource() https://fonts.gstatic.com/s/productsans/v9/HYvgU2fE2nRJvZ5JFAumwRampu5_7CjHW5spxoeN3Vs.woff2 157 | I/DebugWVClient: onPageCommitVisible() https://store.google.com/gb/?hl=en-GB&countryRedirect=true&utm_source=hpp&utm_medium=google_oo&utm_campaign=GS100077 158 | I/DebugWVClient: shouldInterceptRequest() 1/3 CALL : GET https://fonts.gstatic.com/s/productsans/v9/N0c8y_dasvG2CzM7uYqPLshHwsiXhsDb0smKjAA7Bek.woff2 159 | I/DebugWVClient: shouldInterceptRequest() 2/3 REQ HEADERS: {User-Agent=Mozilla/5.0 (Linux; Android 8.0.0; Android SDK built for x86 Build/OSR1.170720.005; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.125 Mobile Safari/537.36, Origin=https://store.google.com, Referer=https://store.google.com/gb/?hl=en-GB&countryRedirect=true&utm_source=hpp&utm_medium=google_oo&utm_campaign=GS100077, Accept=*/*} 160 | I/DebugWVClient: shouldInterceptRequest() 3/3 INTERCEPT : false 161 | I/DebugWVClient: onLoadResource() https://fonts.gstatic.com/s/productsans/v9/N0c8y_dasvG2CzM7uYqPLshHwsiXhsDb0smKjAA7Bek.woff2 162 | I/DebugWVClient: shouldInterceptRequest() 1/3 CALL : GET https://lh3.googleusercontent.com/bxB6wR8V43WB9bMVZ0ILjriCFgCT-MNn2Mz9wPxlH1PyaWbCgBsV-EAPzbyRSfxHRNE=w96-h96 163 | I/DebugWVClient: shouldInterceptRequest() 2/3 REQ HEADERS: {User-Agent=Mozilla/5.0 (Linux; Android 8.0.0; Android SDK built for x86 Build/OSR1.170720.005; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.125 Mobile Safari/537.36, Accept=image/webp,image/*,*/*;q=0.8, Referer=https://store.google.com/gb/?hl=en-GB&countryRedirect=true&utm_source=hpp&utm_medium=google_oo&utm_campaign=GS100077} 164 | I/DebugWVClient: shouldInterceptRequest() 3/3 INTERCEPT : false 165 | I/DebugWVClient: shouldInterceptRequest() 1/3 CALL : GET https://lh3.googleusercontent.com/QJ8E3sNKLviIyxol6UNAjnwmAvlta6fzl94f2Hxqj1vnbvB9LyXKfcT1XatulWFgkbvm=w96-h96 166 | I/DebugWVClient: shouldInterceptRequest() 2/3 REQ HEADERS: {User-Agent=Mozilla/5.0 (Linux; Android 8.0.0; Android SDK built for x86 Build/OSR1.170720.005; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.125 Mobile Safari/537.36, Accept=image/webp,image/*,*/*;q=0.8, Referer=https://store.google.com/gb/?hl=en-GB&countryRedirect=true&utm_source=hpp&utm_medium=google_oo&utm_campaign=GS100077} 167 | I/DebugWVClient: shouldInterceptRequest() 3/3 INTERCEPT : false 168 | I/DebugWVClient: onLoadResource() https://lh3.googleusercontent.com/bxB6wR8V43WB9bMVZ0ILjriCFgCT-MNn2Mz9wPxlH1PyaWbCgBsV-EAPzbyRSfxHRNE=w96-h96 169 | I/DebugWVClient: onLoadResource() https://lh3.googleusercontent.com/QJ8E3sNKLviIyxol6UNAjnwmAvlta6fzl94f2Hxqj1vnbvB9LyXKfcT1XatulWFgkbvm=w96-h96 170 | I/DebugWVClient: shouldInterceptRequest() 1/3 CALL : GET https://lh3.googleusercontent.com/k3ZARl_vWPzBgKaFIL279g2_IQRSWbvCK-eQn52APeZavVDw7__iMRZ9h5Tn9YdKc4s=w96-h96 171 | I/DebugWVClient: shouldInterceptRequest() 2/3 REQ HEADERS: {User-Agent=Mozilla/5.0 (Linux; Android 8.0.0; Android SDK built for x86 Build/OSR1.170720.005; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.125 Mobile Safari/537.36, Accept=image/webp,image/*,*/*;q=0.8, Referer=https://store.google.com/gb/?hl=en-GB&countryRedirect=true&utm_source=hpp&utm_medium=google_oo&utm_campaign=GS100077} 172 | I/DebugWVClient: shouldInterceptRequest() 3/3 INTERCEPT : false 173 | I/DebugWVClient: shouldInterceptRequest() 1/3 CALL : GET https://lh3.googleusercontent.com/JB70a3qUIFB2OiIfgNc7qB69N2N3m68oX1XHTRSuaJWNXJY5ITm2m62lqQ_qD5NDcpU=w96-h96 174 | I/DebugWVClient: shouldInterceptRequest() 2/3 REQ HEADERS: {User-Agent=Mozilla/5.0 (Linux; Android 8.0.0; Android SDK built for x86 Build/OSR1.170720.005; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.125 Mobile Safari/537.36, Accept=image/webp,image/*,*/*;q=0.8, Referer=https://store.google.com/gb/?hl=en-GB&countryRedirect=true&utm_source=hpp&utm_medium=google_oo&utm_campaign=GS100077} 175 | I/DebugWVClient: shouldInterceptRequest() 3/3 INTERCEPT : false 176 | I/DebugWVClient: onLoadResource() https://lh3.googleusercontent.com/k3ZARl_vWPzBgKaFIL279g2_IQRSWbvCK-eQn52APeZavVDw7__iMRZ9h5Tn9YdKc4s=w96-h96 177 | I/DebugWVClient: onLoadResource() https://lh3.googleusercontent.com/JB70a3qUIFB2OiIfgNc7qB69N2N3m68oX1XHTRSuaJWNXJY5ITm2m62lqQ_qD5NDcpU=w96-h96 178 | I/DebugWVClient: shouldInterceptRequest() 2/3 REQ HEADERS: {User-Agent=Mozilla/5.0 (Linux; Android 8.0.0; Android SDK built for x86 Build/OSR1.170720.005; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.125 Mobile Safari/537.36, Accept=image/webp,image/*,*/*;q=0.8, Referer=https://store.google.com/gb/?hl=en-GB&countryRedirect=true&utm_source=hpp&utm_medium=google_oo&utm_campaign=GS100077} 179 | I/DebugWVClient: shouldInterceptRequest() 3/3 INTERCEPT : false 180 | I/DebugWVClient: onLoadResource() https://lh3.googleusercontent.com/GlrKLWut6gWN52kyEtFK85r0ER-paG3TdSDfEGqsNfeYW0gcgLUW0ARYyassu3Y-pxs=w96-h96 181 | I/DebugWVClient: shouldInterceptRequest() 1/3 CALL : GET https://fonts.gstatic.com/s/productsans/v9/N0c8y_dasvG2CzM7uYqPLtCODO6R-QMzjsZRstdx6VU.woff2 182 | I/DebugWVClient: shouldInterceptRequest() 2/3 REQ HEADERS: {User-Agent=Mozilla/5.0 (Linux; Android 8.0.0; Android SDK built for x86 Build/OSR1.170720.005; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.125 Mobile Safari/537.36, Origin=https://store.google.com, Referer=https://store.google.com/gb/?hl=en-GB&countryRedirect=true&utm_source=hpp&utm_medium=google_oo&utm_campaign=GS100077, Accept=*/*} 183 | I/DebugWVClient: shouldInterceptRequest() 3/3 INTERCEPT : false 184 | I/DebugWVClient: onLoadResource() https://fonts.gstatic.com/s/productsans/v9/N0c8y_dasvG2CzM7uYqPLtCODO6R-QMzjsZRstdx6VU.woff2 185 | I/DebugWVClient: shouldInterceptRequest() 1/3 CALL : GET https://www.gstatic.com/images/icons/material/system/2x/arrow_forward_googblue_24dp.png 186 | I/DebugWVClient: shouldInterceptRequest() 2/3 REQ HEADERS: {User-Agent=Mozilla/5.0 (Linux; Android 8.0.0; Android SDK built for x86 Build/OSR1.170720.005; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.125 Mobile Safari/537.36, Accept=image/webp,image/*,*/*;q=0.8, Referer=https://store.google.com/gb/?hl=en-GB&countryRedirect=true&utm_source=hpp&utm_medium=google_oo&utm_campaign=GS100077} 187 | I/DebugWVClient: shouldInterceptRequest() 3/3 INTERCEPT : false 188 | I/DebugWVClient: onLoadResource() https://www.gstatic.com/images/icons/material/system/2x/arrow_forward_googblue_24dp.png 189 | I/DebugWVClient: doUpdateVisitedHistory() https://www.googletagmanager.com/ns.html?id=GTM-MX89MJ, isReload: false 190 | I/DebugWVClient: <--- onPageFinished() https://store.google.com/gb/?hl=en-GB&countryRedirect=true&utm_source=hpp&utm_medium=google_oo&utm_campaign=GS100077 191 | I/DebugWVClient: shouldInterceptRequest() 1/3 CALL : GET https://www.gstatic.com/images/branding/product/1x/googleg_16dp.png 192 | I/DebugWVClient: shouldInterceptRequest() 2/3 REQ HEADERS: {User-Agent=Mozilla/5.0 (Linux; Android 8.0.0; Android SDK built for x86 Build/OSR1.170720.005; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.125 Mobile Safari/537.36, Accept=image/webp,image/*,*/*;q=0.8, Referer=https://store.google.com/gb/?hl=en-GB&countryRedirect=true&utm_source=hpp&utm_medium=google_oo&utm_campaign=GS100077} 193 | I/DebugWVClient: shouldInterceptRequest() 3/3 INTERCEPT : false 194 | I/DebugWVClient: onLoadResource() https://www.gstatic.com/images/branding/product/1x/googleg_16dp.png 195 | I/DebugWVClient: shouldInterceptRequest() 1/3 CALL : GET https://www.gstatic.com/images/branding/product/1x/googleg_32dp.png 196 | I/DebugWVClient: shouldInterceptRequest() 2/3 REQ HEADERS: {User-Agent=Mozilla/5.0 (Linux; Android 8.0.0; Android SDK built for x86 Build/OSR1.170720.005; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.125 Mobile Safari/537.36, Accept=image/webp,image/*,*/*;q=0.8, Referer=https://store.google.com/gb/?hl=en-GB&countryRedirect=true&utm_source=hpp&utm_medium=google_oo&utm_campaign=GS100077} 197 | I/DebugWVClient: shouldInterceptRequest() 3/3 INTERCEPT : false 198 | I/DebugWVClient: onLoadResource() https://www.gstatic.com/images/branding/product/1x/googleg_32dp.png 199 | I/DebugWVClient: shouldInterceptRequest() 1/3 CALL : GET https://www.gstatic.com/images/branding/product/1x/googleg_96dp.png 200 | I/DebugWVClient: shouldInterceptRequest() 2/3 REQ HEADERS: {User-Agent=Mozilla/5.0 (Linux; Android 8.0.0; Android SDK built for x86 Build/OSR1.170720.005; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.125 Mobile Safari/537.36, Accept=image/webp,image/*,*/*;q=0.8, Referer=https://store.google.com/gb/?hl=en-GB&countryRedirect=true&utm_source=hpp&utm_medium=google_oo&utm_campaign=GS100077} 201 | I/DebugWVClient: shouldInterceptRequest() 3/3 INTERCEPT : false 202 | I/DebugWVClient: onLoadResource() https://www.gstatic.com/images/branding/product/1x/googleg_96dp.png 203 | 204 | ``` 205 | 206 | ## License 207 | Copyright (C) 2017 Alexandros Schillings 208 | 209 | Licensed under the Apache License, Version 2.0 (the "License"); 210 | you may not use this file except in compliance with the License. 211 | You may obtain a copy of the License at 212 | 213 | http://www.apache.org/licenses/LICENSE-2.0 214 | 215 | Unless required by applicable law or agreed to in writing, software 216 | distributed under the License is distributed on an "AS IS" BASIS, 217 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 218 | See the License for the specific language governing permissions and 219 | limitations under the License. 220 | --------------------------------------------------------------------------------