1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package org.chromium.android_webview;
6
7import android.content.Context;
8import android.content.pm.ActivityInfo;
9import android.graphics.Bitmap;
10import android.graphics.Picture;
11import android.graphics.Rect;
12import android.graphics.RectF;
13import android.net.http.SslError;
14import android.os.Handler;
15import android.os.Looper;
16import android.os.Message;
17import android.util.Log;
18import android.view.KeyEvent;
19import android.view.View;
20import android.webkit.ConsoleMessage;
21import android.webkit.GeolocationPermissions;
22import android.webkit.SslErrorHandler;
23import android.webkit.ValueCallback;
24import android.webkit.WebChromeClient;
25
26import org.chromium.base.ThreadUtils;
27import org.chromium.content.browser.ContentVideoView;
28import org.chromium.content.browser.ContentVideoViewClient;
29import org.chromium.content.browser.ContentVideoViewControls;
30import org.chromium.content.browser.ContentViewClient;
31import org.chromium.content.browser.ContentViewCore;
32import org.chromium.content.browser.WebContentsObserverAndroid;
33import org.chromium.net.NetError;
34
35/**
36 * Base-class that an AwContents embedder derives from to receive callbacks.
37 * This extends ContentViewClient, as in many cases we want to pass-thru ContentViewCore
38 * callbacks right to our embedder, and this setup facilities that.
39 * For any other callbacks we need to make transformations of (e.g. adapt parameters
40 * or perform filtering) we can provide final overrides for methods here, and then introduce
41 * new abstract methods that the our own client must implement.
42 * i.e.: all methods in this class should either be final, or abstract.
43 */
44public abstract class AwContentsClient {
45
46    private static final String TAG = "AwContentsClient";
47    private final AwContentsClientCallbackHelper mCallbackHelper =
48        new AwContentsClientCallbackHelper(this);
49
50    private AwWebContentsObserver mWebContentsObserver;
51
52    private AwContentViewClient mContentViewClient = new AwContentViewClient();
53
54    // Last background color reported from the renderer. Holds the sentinal value INVALID_COLOR
55    // if not valid.
56    private int mCachedRendererBackgroundColor = INVALID_COLOR;
57
58    private static final int INVALID_COLOR = 0;
59
60    class AwWebContentsObserver extends WebContentsObserverAndroid {
61        public AwWebContentsObserver(ContentViewCore contentViewCore) {
62            super(contentViewCore);
63        }
64
65        @Override
66        public void didStopLoading(final String url) {
67            ThreadUtils.postOnUiThread(new Runnable() {
68                @Override
69                public void run() {
70                    AwContentsClient.this.onPageFinished(url);
71                }
72            });
73        }
74
75        @Override
76        public void didFailLoad(boolean isProvisionalLoad,
77                boolean isMainFrame, int errorCode, String description, String failingUrl) {
78            if (errorCode == NetError.ERR_ABORTED) {
79                // This error code is generated for the following reasons:
80                // - WebView.stopLoading is called,
81                // - the navigation is intercepted by the embedder via shouldOverrideNavigation.
82                //
83                // The Android WebView does not notify the embedder of these situations using this
84                // error code with the WebViewClient.onReceivedError callback.
85                return;
86            }
87            if (!isMainFrame) {
88                // The Android WebView does not notify the embedder of sub-frame failures.
89                return;
90            }
91            AwContentsClient.this.onReceivedError(
92                    ErrorCodeConversionHelper.convertErrorCode(errorCode), description, failingUrl);
93        }
94
95        @Override
96        public void didNavigateAnyFrame(String url, String baseUrl, boolean isReload) {
97            AwContentsClient.this.doUpdateVisitedHistory(url, isReload);
98        }
99
100    }
101
102    private class AwContentViewClient extends ContentViewClient {
103        @Override
104        public void onBackgroundColorChanged(int color) {
105            // Avoid storing the sentinal INVALID_COLOR (note that both 0 and 1 are both
106            // fully transparent so this transpose makes no visible difference).
107            mCachedRendererBackgroundColor = color == INVALID_COLOR ? 1 : color;
108        }
109
110        @Override
111        public void onStartContentIntent(Context context, String contentUrl) {
112            //  Callback when detecting a click on a content link.
113            AwContentsClient.this.shouldOverrideUrlLoading(contentUrl);
114        }
115
116        @Override
117        public void onRendererCrash(boolean crashedWhileOomProtected) {
118            // This is not possible so long as the webview is run single process!
119            throw new RuntimeException("Renderer crash reported.");
120        }
121
122        @Override
123        public void onUpdateTitle(String title) {
124            AwContentsClient.this.onReceivedTitle(title);
125        }
126
127        @Override
128        public boolean shouldOverrideKeyEvent(KeyEvent event) {
129            return AwContentsClient.this.shouldOverrideKeyEvent(event);
130        }
131
132        @Override
133        final public ContentVideoViewClient getContentVideoViewClient() {
134            return new AwContentVideoViewClient();
135        }
136    }
137
138    final void installWebContentsObserver(ContentViewCore contentViewCore) {
139        if (mWebContentsObserver != null) {
140            mWebContentsObserver.detachFromWebContents();
141        }
142        mWebContentsObserver = new AwWebContentsObserver(contentViewCore);
143    }
144
145    private class AwContentVideoViewClient implements ContentVideoViewClient {
146        @Override
147        public void onShowCustomView(View view) {
148            WebChromeClient.CustomViewCallback cb = new WebChromeClient.CustomViewCallback() {
149                @Override
150                public void onCustomViewHidden() {
151                    ContentVideoView contentVideoView = ContentVideoView.getContentVideoView();
152                    if (contentVideoView != null)
153                        contentVideoView.exitFullscreen(false);
154                }
155            };
156            AwContentsClient.this.onShowCustomView(view, cb);
157        }
158
159        @Override
160        public void onDestroyContentVideoView() {
161            AwContentsClient.this.onHideCustomView();
162        }
163
164        @Override
165        public View getVideoLoadingProgressView() {
166            return AwContentsClient.this.getVideoLoadingProgressView();
167        }
168
169        @Override
170        public ContentVideoViewControls createControls() {
171            return null;
172        }
173    }
174
175    final AwContentsClientCallbackHelper getCallbackHelper() {
176        return mCallbackHelper;
177    }
178
179    final ContentViewClient getContentViewClient() {
180        return mContentViewClient;
181    }
182
183    final int getCachedRendererBackgroundColor() {
184        assert isCachedRendererBackgroundColorValid();
185        return mCachedRendererBackgroundColor;
186    }
187
188    final boolean isCachedRendererBackgroundColorValid() {
189        return mCachedRendererBackgroundColor != INVALID_COLOR;
190    }
191
192    //--------------------------------------------------------------------------------------------
193    //             WebView specific methods that map directly to WebViewClient / WebChromeClient
194    //--------------------------------------------------------------------------------------------
195
196    public static class FileChooserParams {
197        public int mode;
198        public String acceptTypes;
199        public String title;
200        public String defaultFilename;
201        public boolean capture;
202    }
203
204    public abstract void getVisitedHistory(ValueCallback<String[]> callback);
205
206    public abstract void doUpdateVisitedHistory(String url, boolean isReload);
207
208    public abstract void onProgressChanged(int progress);
209
210    public abstract InterceptedRequestData shouldInterceptRequest(String url);
211
212    public abstract boolean shouldOverrideKeyEvent(KeyEvent event);
213
214    public abstract boolean shouldOverrideUrlLoading(String url);
215
216    public abstract void onLoadResource(String url);
217
218    public abstract void onUnhandledKeyEvent(KeyEvent event);
219
220    public abstract boolean onConsoleMessage(ConsoleMessage consoleMessage);
221
222    public abstract void onReceivedHttpAuthRequest(AwHttpAuthHandler handler,
223            String host, String realm);
224
225    public abstract void onReceivedSslError(ValueCallback<Boolean> callback, SslError error);
226
227    public abstract void onReceivedLoginRequest(String realm, String account, String args);
228
229    public abstract void onFormResubmission(Message dontResend, Message resend);
230
231    public abstract void onDownloadStart(String url, String userAgent, String contentDisposition,
232            String mimeType, long contentLength);
233
234    // TODO(joth): Make abstract once this has rolled in downstream.
235    public /*abstract*/ void showFileChooser(ValueCallback<String[]> uploadFilePathsCallback,
236            FileChooserParams fileChooserParams) { }
237
238    public abstract void onGeolocationPermissionsShowPrompt(String origin,
239            GeolocationPermissions.Callback callback);
240
241    public abstract void onGeolocationPermissionsHidePrompt();
242
243    public abstract void onScaleChangedScaled(float oldScale, float newScale);
244
245    protected abstract void handleJsAlert(String url, String message, JsResultReceiver receiver);
246
247    protected abstract void handleJsBeforeUnload(String url, String message,
248            JsResultReceiver receiver);
249
250    protected abstract void handleJsConfirm(String url, String message, JsResultReceiver receiver);
251
252    protected abstract void handleJsPrompt(String url, String message, String defaultValue,
253            JsPromptResultReceiver receiver);
254
255    protected abstract boolean onCreateWindow(boolean isDialog, boolean isUserGesture);
256
257    protected abstract void onCloseWindow();
258
259    public abstract void onReceivedTouchIconUrl(String url, boolean precomposed);
260
261    public abstract void onReceivedIcon(Bitmap bitmap);
262
263    public abstract void onReceivedTitle(String title);
264
265    protected abstract void onRequestFocus();
266
267    protected abstract View getVideoLoadingProgressView();
268
269    public abstract void onPageStarted(String url);
270
271    public abstract void onPageFinished(String url);
272
273    public abstract void onReceivedError(int errorCode, String description, String failingUrl);
274
275    // TODO (michaelbai): Remove this method once the same method remove from
276    // WebViewContentsClientAdapter.
277    public void onShowCustomView(View view,
278           int requestedOrientation, WebChromeClient.CustomViewCallback callback) {
279    }
280
281    // TODO (michaelbai): This method should be abstract, having empty body here
282    // makes the merge to the Android easy.
283    public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
284        onShowCustomView(view, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED, callback);
285    }
286
287    public abstract void onHideCustomView();
288
289    public abstract Bitmap getDefaultVideoPoster();
290
291    //--------------------------------------------------------------------------------------------
292    //                              Other WebView-specific methods
293    //--------------------------------------------------------------------------------------------
294    //
295    public abstract void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
296            boolean isDoneCounting);
297
298    /**
299     * Called whenever there is a new content picture available.
300     * @param picture New picture.
301     */
302    public abstract void onNewPicture(Picture picture);
303
304}
305