1 /*
2  * Copyright 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.webkit;
18 
19 import android.os.Build;
20 import android.webkit.SafeBrowsingResponse;
21 import android.webkit.WebResourceError;
22 import android.webkit.WebResourceRequest;
23 import android.webkit.WebResourceResponse;
24 import android.webkit.WebView;
25 import android.webkit.WebViewClient;
26 
27 import androidx.annotation.IntDef;
28 import androidx.annotation.NonNull;
29 import androidx.annotation.RequiresApi;
30 import androidx.annotation.RestrictTo;
31 import androidx.webkit.internal.WebResourceErrorImpl;
32 import androidx.webkit.internal.WebViewFeatureInternal;
33 
34 import org.chromium.support_lib_boundary.WebViewClientBoundaryInterface;
35 import org.chromium.support_lib_boundary.util.Features;
36 
37 import java.lang.annotation.Retention;
38 import java.lang.annotation.RetentionPolicy;
39 import java.lang.reflect.InvocationHandler;
40 
41 /**
42  * Compatibility version of {@link android.webkit.WebViewClient}.
43  */
44 // Note: some methods are marked as RequiresApi 21, because only an up-to-date WebView APK would
45 // ever invoke these methods (and WebView can only be updated on Lollipop and above). The app can
46 // still construct a WebViewClientCompat on a pre-Lollipop devices, and explicitly invoke these
47 // methods, so each of these methods must also handle this case.
48 public class WebViewClientCompat extends WebViewClient implements WebViewClientBoundaryInterface {
49     private static final String[] sSupportedFeatures = new String[] {
50         Features.VISUAL_STATE_CALLBACK,
51         Features.RECEIVE_WEB_RESOURCE_ERROR,
52         Features.RECEIVE_HTTP_ERROR,
53         Features.SHOULD_OVERRIDE_WITH_REDIRECTS,
54         Features.SAFE_BROWSING_HIT,
55     };
56 
57     /** @hide */
58     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
59     @IntDef(value = {
60             WebViewClient.SAFE_BROWSING_THREAT_UNKNOWN,
61             WebViewClient.SAFE_BROWSING_THREAT_MALWARE,
62             WebViewClient.SAFE_BROWSING_THREAT_PHISHING,
63             WebViewClient.SAFE_BROWSING_THREAT_UNWANTED_SOFTWARE
64     })
65     @Retention(RetentionPolicy.SOURCE)
66     public @interface SafeBrowsingThreat {}
67 
68     /**
69      * Returns the list of features this client supports. This feature list should always be a
70      * subset of the Features declared in WebViewFeature.
71      *
72      * @hide
73      */
74     @Override
75     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
getSupportedFeatures()76     public final String[] getSupportedFeatures() {
77         return sSupportedFeatures;
78     }
79 
80     /**
81      * Notify the host application that {@link android.webkit.WebView} content left over from
82      * previous page navigations will no longer be drawn.
83      *
84      * <p>This callback can be used to determine the point at which it is safe to make a recycled
85      * {@link android.webkit.WebView} visible, ensuring that no stale content is shown. It is called
86      * at the earliest point at which it can be guaranteed that {@link WebView#onDraw} will no
87      * longer draw any content from previous navigations. The next draw will display either the
88      * {@link WebView#setBackgroundColor background color} of the {@link WebView}, or some of the
89      * contents of the newly loaded page.
90      *
91      * <p>This method is called when the body of the HTTP response has started loading, is reflected
92      * in the DOM, and will be visible in subsequent draws. This callback occurs early in the
93      * document loading process, and as such you should expect that linked resources (for example,
94      * CSS and images) may not be available.
95      *
96      * <p>For more fine-grained notification of visual state updates, see {@link
97      * WebViewCompat#postVisualStateCallback}.
98      *
99      * <p>Please note that all the conditions and recommendations applicable to
100      * {@link WebViewCompat#postVisualStateCallback} also apply to this API.
101      *
102      * <p>This callback is only called for main frame navigations.
103      *
104      * <p>This method is called only if {@link WebViewFeature#VISUAL_STATE_CALLBACK} is supported.
105      * You can check whether that flag is supported using {@link
106      * WebViewFeature#isFeatureSupported(String)}.
107      *
108      * @param view The {@link android.webkit.WebView} for which the navigation occurred.
109      * @param url  The URL corresponding to the page navigation that triggered this callback.
110      */
111     @Override
onPageCommitVisible(@onNull WebView view, @NonNull String url)112     public void onPageCommitVisible(@NonNull WebView view, @NonNull String url) {
113     }
114 
115     /**
116      * Invoked by chromium (for WebView APks 67+) for the {@code onReceivedError} event.
117      * Applications are not meant to override this, and should instead override the non-final {@link
118      * onReceivedError(WebView, WebResourceRequest, WebResourceErrorCompat)} method.
119      *
120      * @hide
121      */
122     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
123     @Override
124     @RequiresApi(21)
onReceivedError(@onNull WebView view, @NonNull WebResourceRequest request, @NonNull InvocationHandler handler)125     public final void onReceivedError(@NonNull WebView view, @NonNull WebResourceRequest request,
126             /* WebResourceError */ @NonNull InvocationHandler handler) {
127         onReceivedError(view, request, new WebResourceErrorImpl(handler));
128     }
129 
130     /**
131      * Invoked by chromium (in legacy WebView APKs) for the {@code onReceivedError} event on {@link
132      * Build.VERSION_CODES.M} and above. Applications are not meant to override this, and should
133      * instead override the non-final {@link onReceivedError(WebView, WebResourceRequest,
134      * WebResourceErrorCompat)} method.
135      *
136      * @hide
137      */
138     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
139     @Override
140     @RequiresApi(23)
onReceivedError(@onNull WebView view, @NonNull WebResourceRequest request, @NonNull WebResourceError error)141     public final void onReceivedError(@NonNull WebView view, @NonNull WebResourceRequest request,
142             @NonNull WebResourceError error) {
143         if (Build.VERSION.SDK_INT < 23) return;
144         onReceivedError(view, request, new WebResourceErrorImpl(error));
145     }
146 
147     /**
148      * Report web resource loading error to the host application. These errors usually indicate
149      * inability to connect to the server. Note that unlike the deprecated version of the callback,
150      * the new version will be called for any resource (iframe, image, etc.), not just for the main
151      * page. Thus, it is recommended to perform minimum required work in this callback.
152      *
153      * <p>This method is called only if {@link WebViewFeature#RECEIVE_WEB_RESOURCE_ERROR} is
154      * supported. You can check whether that flag is supported using {@link
155      * WebViewFeature#isFeatureSupported(String)}.
156      *
157      * @param view The WebView that is initiating the callback.
158      * @param request The originating request.
159      * @param error Information about the error occurred.
160      */
161     @SuppressWarnings("deprecation") // for invoking the old onReceivedError.
162     @RequiresApi(21)
onReceivedError(@onNull WebView view, @NonNull WebResourceRequest request, @NonNull WebResourceErrorCompat error)163     public void onReceivedError(@NonNull WebView view, @NonNull WebResourceRequest request,
164             @NonNull WebResourceErrorCompat error) {
165         if (Build.VERSION.SDK_INT < 21) return;
166         if (!WebViewFeature.isFeatureSupported(WebViewFeature.WEB_RESOURCE_ERROR_GET_CODE)
167                 || !WebViewFeature.isFeatureSupported(
168                         WebViewFeature.WEB_RESOURCE_ERROR_GET_DESCRIPTION)) {
169             // If the WebView APK drops supports for these APIs in the future, simply do nothing.
170             return;
171         }
172         if (request.isForMainFrame()) {
173             onReceivedError(view,
174                     error.getErrorCode(), error.getDescription().toString(),
175                     request.getUrl().toString());
176         }
177     }
178 
179     /**
180      * Notify the host application that an HTTP error has been received from the server while
181      * loading a resource.  HTTP errors have status codes &gt;= 400.  This callback will be called
182      * for any resource (iframe, image, etc.), not just for the main page. Thus, it is recommended
183      * to perform minimum required work in this callback. Note that the content of the server
184      * response may not be provided within the {@code errorResponse} parameter.
185      *
186      * <p>This method is called only if {@link WebViewFeature#RECEIVE_HTTP_ERROR} is supported. You
187      * can check whether that flag is supported using {@link
188      * WebViewFeature#isFeatureSupported(String)}.
189      *
190      * @param view The WebView that is initiating the callback.
191      * @param request The originating request.
192      * @param errorResponse Information about the error occurred.
193      */
194     @Override
onReceivedHttpError(@onNull WebView view, @NonNull WebResourceRequest request, @NonNull WebResourceResponse errorResponse)195     public void onReceivedHttpError(@NonNull WebView view, @NonNull WebResourceRequest request,
196             @NonNull WebResourceResponse errorResponse) {
197     }
198 
199     /**
200      * Invoked by chromium (for WebView APks 67+) for the {@code onSafeBrowsingHit} event.
201      * Applications are not meant to override this, and should instead override the non-final {@link
202      * onSafeBrowsingHit(WebView, WebResourceRequest, int, SafeBrowsingResponseCompat)} method.
203      *
204      * @hide
205      */
206     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
207     @Override
onSafeBrowsingHit(@onNull WebView view, @NonNull WebResourceRequest request, @SafeBrowsingThreat int threatType, @NonNull InvocationHandler handler)208     public final void onSafeBrowsingHit(@NonNull WebView view, @NonNull WebResourceRequest request,
209             @SafeBrowsingThreat int threatType,
210             /* SafeBrowsingResponse */ @NonNull InvocationHandler handler) {
211         onSafeBrowsingHit(view, request, threatType,
212                 SafeBrowsingResponseCompat.fromInvocationHandler(handler));
213     }
214 
215     /**
216      * Invoked by chromium (in legacy WebView APKs) for the {@code onSafeBrowsingHit} event on
217      * {@link Build.VERSION_CODES.O_MR1} and above. Applications are not meant to override this, and
218      * should instead override the non-final {@link onSafeBrowsingHit(WebView, WebResourceRequest,
219      * int, SafeBrowsingResponseCompat)} method.
220      *
221      * @hide
222      */
223     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
224     @Override
225     @RequiresApi(27)
onSafeBrowsingHit(@onNull WebView view, @NonNull WebResourceRequest request, @SafeBrowsingThreat int threatType, @NonNull SafeBrowsingResponse response)226     public final void onSafeBrowsingHit(@NonNull WebView view, @NonNull WebResourceRequest request,
227             @SafeBrowsingThreat int threatType, @NonNull SafeBrowsingResponse response) {
228         onSafeBrowsingHit(view, request, threatType,
229                 SafeBrowsingResponseCompat.fromSafeBrowsingResponse(response));
230     }
231 
232     /**
233      * Notify the host application that a loading URL has been flagged by Safe Browsing.
234      *
235      * The application must invoke the callback to indicate the preferred response. The default
236      * behavior is to show an interstitial to the user, with the reporting checkbox visible.
237      *
238      * If the application needs to show its own custom interstitial UI, the callback can be invoked
239      * asynchronously with {@link SafeBrowsingResponseCompat#backToSafety} or {@link
240      * SafeBrowsingResponseCompat#proceed}, depending on user response.
241      *
242      * @param view The WebView that hit the malicious resource.
243      * @param request Object containing the details of the request.
244      * @param threatType The reason the resource was caught by Safe Browsing, corresponding to a
245      *                   {@code SAFE_BROWSING_THREAT_*} value.
246      * @param callback Applications must invoke one of the callback methods.
247      */
onSafeBrowsingHit(@onNull WebView view, @NonNull WebResourceRequest request, @SafeBrowsingThreat int threatType, @NonNull SafeBrowsingResponseCompat callback)248     public void onSafeBrowsingHit(@NonNull WebView view, @NonNull WebResourceRequest request,
249             @SafeBrowsingThreat int threatType, @NonNull SafeBrowsingResponseCompat callback) {
250         if (WebViewFeature.isFeatureSupported(
251                 WebViewFeature.SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL)) {
252             callback.showInterstitial(true);
253         } else {
254             // This should not happen, but in case the WebView APK eventually drops support for
255             // showInterstitial(), raise a runtime exception and require the WebView APK to handle
256             // this.
257             throw WebViewFeatureInternal.getUnsupportedOperationException();
258         }
259     }
260 
261     /**
262      * Give the host application a chance to take over the control when a new
263      * url is about to be loaded in the current WebView. If WebViewClient is not
264      * provided, by default WebView will ask Activity Manager to choose the
265      * proper handler for the url. If WebViewClient is provided, return {@code true}
266      * means the host application handles the url, while return {@code false} means the
267      * current WebView handles the url.
268      *
269      * <p>Notes:
270      * <ul>
271      * <li>This method is not called for requests using the POST &quot;method&quot;.</li>
272      * <li>This method is also called for subframes with non-http schemes, thus it is
273      * strongly disadvised to unconditionally call {@link WebView#loadUrl(String)}
274      * with the request's url from inside the method and then return {@code true},
275      * as this will make WebView to attempt loading a non-http url, and thus fail.</li>
276      * </ul>
277      *
278      * <p>This method is called only if {@link WebViewFeature#SHOULD_OVERRIDE_WITH_REDIRECTS} is
279      * supported. You can check whether that flag is supported using {@link
280      * WebViewFeature#isFeatureSupported(String)}.
281      *
282      * @param view The WebView that is initiating the callback.
283      * @param request Object containing the details of the request.
284      * @return {@code true} if the host application wants to leave the current WebView
285      *         and handle the url itself, otherwise return {@code false}.
286      */
287     @Override
288     @SuppressWarnings("deprecation") // for invoking the old shouldOverrideUrlLoading.
289     @RequiresApi(21)
shouldOverrideUrlLoading(@onNull WebView view, @NonNull WebResourceRequest request)290     public boolean shouldOverrideUrlLoading(@NonNull WebView view,
291             @NonNull WebResourceRequest request) {
292         if (Build.VERSION.SDK_INT < 21) return false;
293         return shouldOverrideUrlLoading(view, request.getUrl().toString());
294     }
295 }
296