WebViewContentsClientAdapter.java revision 7a7dce808e1545e859b80b86f0279ee68ce3f0cc
1/* 2 * Copyright (C) 2012 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 17package com.android.webview.chromium; 18 19import android.content.ActivityNotFoundException; 20import android.content.Context; 21import android.content.Intent; 22import android.graphics.Bitmap; 23import android.graphics.BitmapFactory; 24import android.graphics.Canvas; 25import android.graphics.Color; 26import android.graphics.Picture; 27import android.net.http.ErrorStrings; 28import android.net.http.SslError; 29import android.net.Uri; 30import android.os.Build; 31import android.os.Handler; 32import android.os.Looper; 33import android.os.Message; 34import android.provider.Browser; 35import android.util.Log; 36import android.view.KeyEvent; 37import android.view.View; 38import android.webkit.ConsoleMessage; 39import android.webkit.DownloadListener; 40import android.webkit.GeolocationPermissions; 41import android.webkit.JsDialogHelper; 42import android.webkit.JsPromptResult; 43import android.webkit.JsResult; 44import android.webkit.SslErrorHandler; 45import android.webkit.ValueCallback; 46import android.webkit.WebChromeClient; 47import android.webkit.WebChromeClient.CustomViewCallback; 48import android.webkit.WebResourceResponse; 49import android.webkit.WebView; 50import android.webkit.WebViewClient; 51 52import org.chromium.android_webview.AwContentsClient; 53import org.chromium.android_webview.AwHttpAuthHandler; 54import org.chromium.android_webview.InterceptedRequestData; 55import org.chromium.android_webview.JsPromptResultReceiver; 56import org.chromium.android_webview.JsResultReceiver; 57import org.chromium.base.ThreadUtils; 58import org.chromium.content.browser.ContentView; 59import org.chromium.content.browser.ContentViewClient; 60import org.chromium.content.common.TraceEvent; 61 62import java.net.URISyntaxException; 63import java.util.concurrent.atomic.AtomicBoolean; 64 65 66/** 67 * An adapter class that forwards the callbacks from {@link ContentViewClient} 68 * to the appropriate {@link WebViewClient} or {@link WebChromeClient}. 69 * 70 * An instance of this class is associated with one {@link WebViewChromium} 71 * instance. A WebViewChromium is a WebView implementation provider (that is 72 * android.webkit.WebView delegates all functionality to it) and has exactly 73 * one corresponding {@link ContentView} instance. 74 * 75 * A {@link ContentViewClient} may be shared between multiple {@link ContentView}s, 76 * and hence multiple WebViews. Many WebViewClient methods pass the source 77 * WebView as an argument. This means that we either need to pass the 78 * corresponding ContentView to the corresponding ContentViewClient methods, 79 * or use an instance of ContentViewClientAdapter per WebViewChromium, to 80 * allow the source WebView to be injected by ContentViewClientAdapter. We 81 * choose the latter, because it makes for a cleaner design. 82 */ 83public class WebViewContentsClientAdapter extends AwContentsClient { 84 // TAG is chosen for consistency with classic webview tracing. 85 private static final String TAG = "WebViewCallback"; 86 // Enables API callback tracing 87 private static final boolean TRACE = android.webkit.DebugFlags.TRACE_CALLBACK; 88 // The WebView instance that this adapter is serving. 89 private final WebView mWebView; 90 // The WebViewClient instance that was passed to WebView.setWebViewClient(). 91 private WebViewClient mWebViewClient; 92 // The WebChromeClient instance that was passed to WebView.setContentViewClient(). 93 private WebChromeClient mWebChromeClient; 94 // The listener receiving find-in-page API results. 95 private WebView.FindListener mFindListener; 96 // The listener receiving notifications of screen updates. 97 private WebView.PictureListener mPictureListener; 98 99 private DownloadListener mDownloadListener; 100 101 private Handler mUiThreadHandler; 102 103 private static final int NEW_WEBVIEW_CREATED = 100; 104 105 /** 106 * Adapter constructor. 107 * 108 * @param webView the {@link WebView} instance that this adapter is serving. 109 */ 110 WebViewContentsClientAdapter(WebView webView) { 111 if (webView == null) { 112 throw new IllegalArgumentException("webView can't be null"); 113 } 114 115 mWebView = webView; 116 setWebViewClient(null); 117 118 mUiThreadHandler = new Handler() { 119 120 @Override 121 public void handleMessage(Message msg) { 122 switch(msg.what) { 123 case NEW_WEBVIEW_CREATED: 124 WebView.WebViewTransport t = (WebView.WebViewTransport) msg.obj; 125 WebView newWebView = t.getWebView(); 126 if (newWebView == mWebView) { 127 throw new IllegalArgumentException( 128 "Parent WebView cannot host it's own popup window. Please " + 129 "use WebSettings.setSupportMultipleWindows(false)"); 130 } 131 132 if (newWebView != null && newWebView.copyBackForwardList().getSize() != 0) { 133 throw new IllegalArgumentException( 134 "New WebView for popup window must not have been previously " + 135 "navigated."); 136 } 137 138 WebViewChromium.completeWindowCreation(mWebView, newWebView); 139 break; 140 default: 141 throw new IllegalStateException(); 142 } 143 } 144 }; 145 146 } 147 148 // WebViewClassic is coded in such a way that even if a null WebViewClient is set, 149 // certain actions take place. 150 // We choose to replicate this behavior by using a NullWebViewClient implementation (also known 151 // as the Null Object pattern) rather than duplicating the WebViewClassic approach in 152 // ContentView. 153 static class NullWebViewClient extends WebViewClient { 154 @Override 155 public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) { 156 // TODO: Investigate more and add a test case. 157 // This is a copy of what Clank does. The WebViewCore key handling code and Clank key 158 // handling code differ enough that it's not trivial to figure out how keycodes are 159 // being filtered. 160 int keyCode = event.getKeyCode(); 161 if (keyCode == KeyEvent.KEYCODE_MENU || 162 keyCode == KeyEvent.KEYCODE_HOME || 163 keyCode == KeyEvent.KEYCODE_BACK || 164 keyCode == KeyEvent.KEYCODE_CALL || 165 keyCode == KeyEvent.KEYCODE_ENDCALL || 166 keyCode == KeyEvent.KEYCODE_POWER || 167 keyCode == KeyEvent.KEYCODE_HEADSETHOOK || 168 keyCode == KeyEvent.KEYCODE_CAMERA || 169 keyCode == KeyEvent.KEYCODE_FOCUS || 170 keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || 171 keyCode == KeyEvent.KEYCODE_VOLUME_MUTE || 172 keyCode == KeyEvent.KEYCODE_VOLUME_UP) { 173 return true; 174 } 175 return false; 176 } 177 178 @Override 179 public boolean shouldOverrideUrlLoading(WebView view, String url) { 180 Intent intent; 181 // Perform generic parsing of the URI to turn it into an Intent. 182 try { 183 intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME); 184 } catch (URISyntaxException ex) { 185 Log.w(TAG, "Bad URI " + url + ": " + ex.getMessage()); 186 return false; 187 } 188 // Sanitize the Intent, ensuring web pages can not bypass browser 189 // security (only access to BROWSABLE activities). 190 intent.addCategory(Intent.CATEGORY_BROWSABLE); 191 intent.setComponent(null); 192 Intent selector = intent.getSelector(); 193 if (selector != null) { 194 selector.addCategory(Intent.CATEGORY_BROWSABLE); 195 selector.setComponent(null); 196 } 197 // Pass the package name as application ID so that the intent from the 198 // same application can be opened in the same tab. 199 intent.putExtra(Browser.EXTRA_APPLICATION_ID, 200 view.getContext().getPackageName()); 201 try { 202 view.getContext().startActivity(intent); 203 } catch (ActivityNotFoundException ex) { 204 Log.w(TAG, "No application can handle " + url); 205 return false; 206 } 207 return true; 208 } 209 } 210 211 void setWebViewClient(WebViewClient client) { 212 if (client != null) { 213 mWebViewClient = client; 214 } else { 215 mWebViewClient = new NullWebViewClient(); 216 } 217 } 218 219 void setWebChromeClient(WebChromeClient client) { 220 mWebChromeClient = client; 221 } 222 223 void setDownloadListener(DownloadListener listener) { 224 mDownloadListener = listener; 225 } 226 227 void setFindListener(WebView.FindListener listener) { 228 mFindListener = listener; 229 } 230 231 void setPictureListener(WebView.PictureListener listener) { 232 mPictureListener = listener; 233 } 234 235 //-------------------------------------------------------------------------------------------- 236 // Adapter for all the methods. 237 //-------------------------------------------------------------------------------------------- 238 239 /** 240 * @see AwContentsClient#getVisitedHistory 241 */ 242 @Override 243 public void getVisitedHistory(ValueCallback<String[]> callback) { 244 TraceEvent.begin(); 245 if (mWebChromeClient != null) { 246 if (TRACE) Log.d(TAG, "getVisitedHistory"); 247 mWebChromeClient.getVisitedHistory(callback); 248 } 249 TraceEvent.end(); 250 } 251 252 /** 253 * @see AwContentsClient#doUpdateVisiteHistory(String, boolean) 254 */ 255 @Override 256 public void doUpdateVisitedHistory(String url, boolean isReload) { 257 TraceEvent.begin(); 258 if (TRACE) Log.d(TAG, "doUpdateVisitedHistory=" + url + " reload=" + isReload); 259 mWebViewClient.doUpdateVisitedHistory(mWebView, url, isReload); 260 TraceEvent.end(); 261 } 262 263 /** 264 * @see AwContentsClient#onProgressChanged(int) 265 */ 266 @Override 267 public void onProgressChanged(int progress) { 268 TraceEvent.begin(); 269 if (mWebChromeClient != null) { 270 if (TRACE) Log.d(TAG, "onProgressChanged=" + progress); 271 mWebChromeClient.onProgressChanged(mWebView, progress); 272 } 273 TraceEvent.end(); 274 } 275 276 /** 277 * @see AwContentsClient#shouldInterceptRequest(java.lang.String) 278 */ 279 @Override 280 public InterceptedRequestData shouldInterceptRequest(String url) { 281 TraceEvent.begin(); 282 if (TRACE) Log.d(TAG, "shouldInterceptRequest=" + url); 283 WebResourceResponse response = mWebViewClient.shouldInterceptRequest(mWebView, url); 284 TraceEvent.end(); 285 if (response == null) return null; 286 return new InterceptedRequestData( 287 response.getMimeType(), 288 response.getEncoding(), 289 response.getData()); 290 } 291 292 /** 293 * @see AwContentsClient#shouldOverrideUrlLoading(java.lang.String) 294 */ 295 @Override 296 public boolean shouldOverrideUrlLoading(String url) { 297 TraceEvent.begin(); 298 if (TRACE) Log.d(TAG, "shouldOverrideUrlLoading=" + url); 299 boolean result = mWebViewClient.shouldOverrideUrlLoading(mWebView, url); 300 TraceEvent.end(); 301 return result; 302 } 303 304 /** 305 * @see AwContentsClient#onUnhandledKeyEvent(android.view.KeyEvent) 306 */ 307 @Override 308 public void onUnhandledKeyEvent(KeyEvent event) { 309 TraceEvent.begin(); 310 if (TRACE) Log.d(TAG, "onUnhandledKeyEvent"); 311 mWebViewClient.onUnhandledKeyEvent(mWebView, event); 312 TraceEvent.end(); 313 } 314 315 /** 316 * @see AwContentsClient#onConsoleMessage(android.webkit.ConsoleMessage) 317 */ 318 @Override 319 public boolean onConsoleMessage(ConsoleMessage consoleMessage) { 320 TraceEvent.begin(); 321 boolean result; 322 if (mWebChromeClient != null) { 323 if (TRACE) Log.d(TAG, "onConsoleMessage: " + consoleMessage.message()); 324 result = mWebChromeClient.onConsoleMessage(consoleMessage); 325 String message = consoleMessage.message(); 326 if (result && message != null && message.startsWith("[blocked]")) { 327 Log.e(TAG, "Blocked URL: " + message); 328 } 329 } else { 330 result = false; 331 } 332 TraceEvent.end(); 333 return result; 334 } 335 336 /** 337 * @see AwContentsClient#onFindResultReceived(int,int,boolean) 338 */ 339 @Override 340 public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, 341 boolean isDoneCounting) { 342 if (mFindListener == null) return; 343 TraceEvent.begin(); 344 if (TRACE) Log.d(TAG, "onFindResultReceived"); 345 mFindListener.onFindResultReceived(activeMatchOrdinal, numberOfMatches, isDoneCounting); 346 TraceEvent.end(); 347 } 348 349 /** 350 * @See AwContentsClient#onNewPicture(Picture) 351 */ 352 @Override 353 public void onNewPicture(Picture picture) { 354 if (mPictureListener == null) return; 355 TraceEvent.begin(); 356 if (TRACE) Log.d(TAG, "onNewPicture"); 357 mPictureListener.onNewPicture(mWebView, picture); 358 TraceEvent.end(); 359 } 360 361 @Override 362 public void onLoadResource(String url) { 363 TraceEvent.begin(); 364 if (TRACE) Log.d(TAG, "onLoadResource=" + url); 365 mWebViewClient.onLoadResource(mWebView, url); 366 TraceEvent.end(); 367 } 368 369 @Override 370 public boolean onCreateWindow(boolean isDialog, boolean isUserGesture) { 371 Message m = mUiThreadHandler.obtainMessage( 372 NEW_WEBVIEW_CREATED, mWebView.new WebViewTransport()); 373 TraceEvent.begin(); 374 boolean result; 375 if (mWebChromeClient != null) { 376 if (TRACE) Log.d(TAG, "onCreateWindow"); 377 result = mWebChromeClient.onCreateWindow(mWebView, isDialog, isUserGesture, m); 378 } else { 379 result = false; 380 } 381 TraceEvent.end(); 382 return result; 383 } 384 385 /** 386 * @see AwContentsClient#onCloseWindow() 387 */ 388 @Override 389 public void onCloseWindow() { 390 TraceEvent.begin(); 391 if (mWebChromeClient != null) { 392 if (TRACE) Log.d(TAG, "onCloseWindow"); 393 mWebChromeClient.onCloseWindow(mWebView); 394 } 395 TraceEvent.end(); 396 } 397 398 /** 399 * @see AwContentsClient#onRequestFocus() 400 */ 401 @Override 402 public void onRequestFocus() { 403 TraceEvent.begin(); 404 if (mWebChromeClient != null) { 405 if (TRACE) Log.d(TAG, "onRequestFocus"); 406 mWebChromeClient.onRequestFocus(mWebView); 407 } 408 TraceEvent.end(); 409 } 410 411 /** 412 * @see AwContentsClient#onReceivedTouchIconUrl(String url, boolean precomposed) 413 */ 414 @Override 415 public void onReceivedTouchIconUrl(String url, boolean precomposed) { 416 TraceEvent.begin(); 417 if (mWebChromeClient != null) { 418 if (TRACE) Log.d(TAG, "onReceivedTouchIconUrl=" + url); 419 mWebChromeClient.onReceivedTouchIconUrl(mWebView, url, precomposed); 420 } 421 TraceEvent.end(); 422 } 423 424 /** 425 * @see AwContentsClient#onReceivedIcon(Bitmap bitmap) 426 */ 427 @Override 428 public void onReceivedIcon(Bitmap bitmap) { 429 TraceEvent.begin(); 430 if (mWebChromeClient != null) { 431 if (TRACE) Log.d(TAG, "onReceivedIcon"); 432 mWebChromeClient.onReceivedIcon(mWebView, bitmap); 433 } 434 TraceEvent.end(); 435 } 436 437 /** 438 * @see ContentViewClient#onPageStarted(String) 439 */ 440 @Override 441 public void onPageStarted(String url) { 442 TraceEvent.begin(); 443 if (TRACE) Log.d(TAG, "onPageStarted=" + url); 444 mWebViewClient.onPageStarted(mWebView, url, mWebView.getFavicon()); 445 TraceEvent.end(); 446 } 447 448 /** 449 * @see ContentViewClient#onPageFinished(String) 450 */ 451 @Override 452 public void onPageFinished(String url) { 453 TraceEvent.begin(); 454 if (TRACE) Log.d(TAG, "onPageFinished=" + url); 455 mWebViewClient.onPageFinished(mWebView, url); 456 TraceEvent.end(); 457 458 // See b/8208948 459 // This fakes an onNewPicture callback after onPageFinished to allow 460 // CTS tests to run in an un-flaky manner. This is required as the 461 // path for sending Picture updates in Chromium are decoupled from the 462 // page loading callbacks, i.e. the Chrome compositor may draw our 463 // content and send the Picture before onPageStarted or onPageFinished 464 // are invoked. The CTS harness discards any pictures it receives before 465 // onPageStarted is invoked, so in the case we get the Picture before that and 466 // no further updates after onPageStarted, we'll fail the test by timing 467 // out waiting for a Picture. 468 // To ensure backwards compatibility, we need to defer sending Picture updates 469 // until onPageFinished has been invoked. This work is being done 470 // upstream, and we can revert this hack when it lands. 471 if (mPictureListener != null) { 472 ThreadUtils.postOnUiThreadDelayed(new Runnable() { 473 @Override 474 public void run() { 475 UnimplementedWebViewApi.invoke(); 476 if (mPictureListener != null) { 477 if (TRACE) Log.d(TAG, "onPageFinished-fake"); 478 mPictureListener.onNewPicture(mWebView, new Picture()); 479 } 480 } 481 }, 100); 482 } 483 } 484 485 /** 486 * @see ContentViewClient#onReceivedError(int,String,String) 487 */ 488 @Override 489 public void onReceivedError(int errorCode, String description, String failingUrl) { 490 if (description == null || description.isEmpty()) { 491 // ErrorStrings is @hidden, so we can't do this in AwContents. 492 // Normally the net/ layer will set a valid description, but for synthesized callbacks 493 // (like in the case for intercepted requests) AwContents will pass in null. 494 description = ErrorStrings.getString(errorCode, mWebView.getContext()); 495 } 496 TraceEvent.begin(); 497 if (TRACE) Log.d(TAG, "onReceivedError=" + failingUrl); 498 mWebViewClient.onReceivedError(mWebView, errorCode, description, failingUrl); 499 TraceEvent.end(); 500 } 501 502 /** 503 * @see ContentViewClient#onReceivedTitle(String) 504 */ 505 @Override 506 public void onReceivedTitle(String title) { 507 TraceEvent.begin(); 508 if (mWebChromeClient != null) { 509 if (TRACE) Log.d(TAG, "onReceivedTitle"); 510 mWebChromeClient.onReceivedTitle(mWebView, title); 511 } 512 TraceEvent.end(); 513 } 514 515 516 /** 517 * @see ContentViewClient#shouldOverrideKeyEvent(KeyEvent) 518 */ 519 @Override 520 public boolean shouldOverrideKeyEvent(KeyEvent event) { 521 // TODO(joth): The expression here is a workaround for http://b/7697782 :- 522 // 1. The check for system key should be made in AwContents or ContentViewCore, 523 // before shouldOverrideKeyEvent() is called at all. 524 // 2. shouldOverrideKeyEvent() should be called in onKeyDown/onKeyUp, not from 525 // dispatchKeyEvent(). 526 if (event.isSystem()) return true; 527 TraceEvent.begin(); 528 if (TRACE) Log.d(TAG, "shouldOverrideKeyEvent"); 529 boolean result = mWebViewClient.shouldOverrideKeyEvent(mWebView, event); 530 TraceEvent.end(); 531 return result; 532 } 533 534 535 /** 536 * @see ContentViewClient#onStartContentIntent(Context, String) 537 * Callback when detecting a click on a content link. 538 */ 539 // TODO: Delete this method when removed from base class. 540 public void onStartContentIntent(Context context, String contentUrl) { 541 TraceEvent.begin(); 542 if (TRACE) Log.d(TAG, "shouldOverrideUrlLoading=" + contentUrl); 543 mWebViewClient.shouldOverrideUrlLoading(mWebView, contentUrl); 544 TraceEvent.end(); 545 } 546 547 @Override 548 public void onGeolocationPermissionsShowPrompt(String origin, 549 GeolocationPermissions.Callback callback) { 550 TraceEvent.begin(); 551 if (mWebChromeClient != null) { 552 if (TRACE) Log.d(TAG, "onGeolocationPermissionsShowPrompt"); 553 mWebChromeClient.onGeolocationPermissionsShowPrompt(origin, callback); 554 } 555 TraceEvent.end(); 556 } 557 558 @Override 559 public void onGeolocationPermissionsHidePrompt() { 560 TraceEvent.begin(); 561 if (mWebChromeClient != null) { 562 if (TRACE) Log.d(TAG, "onGeolocationPermissionsHidePrompt"); 563 mWebChromeClient.onGeolocationPermissionsHidePrompt(); 564 } 565 TraceEvent.end(); 566 } 567 568 private static class JsPromptResultReceiverAdapter implements JsResult.ResultReceiver { 569 private JsPromptResultReceiver mChromePromptResultReceiver; 570 private JsResultReceiver mChromeResultReceiver; 571 // We hold onto the JsPromptResult here, just to avoid the need to downcast 572 // in onJsResultComplete. 573 private final JsPromptResult mPromptResult = new JsPromptResult(this); 574 575 public JsPromptResultReceiverAdapter(JsPromptResultReceiver receiver) { 576 mChromePromptResultReceiver = receiver; 577 } 578 579 public JsPromptResultReceiverAdapter(JsResultReceiver receiver) { 580 mChromeResultReceiver = receiver; 581 } 582 583 public JsPromptResult getPromptResult() { 584 return mPromptResult; 585 } 586 587 @Override 588 public void onJsResultComplete(JsResult result) { 589 if (mChromePromptResultReceiver != null) { 590 if (mPromptResult.getResult()) { 591 mChromePromptResultReceiver.confirm(mPromptResult.getStringResult()); 592 } else { 593 mChromePromptResultReceiver.cancel(); 594 } 595 } else { 596 if (mPromptResult.getResult()) { 597 mChromeResultReceiver.confirm(); 598 } else { 599 mChromeResultReceiver.cancel(); 600 } 601 } 602 } 603 } 604 605 @Override 606 public void handleJsAlert(String url, String message, JsResultReceiver receiver) { 607 TraceEvent.begin(); 608 if (mWebChromeClient != null) { 609 final JsPromptResult res = 610 new JsPromptResultReceiverAdapter(receiver).getPromptResult(); 611 if (TRACE) Log.d(TAG, "onJsAlert"); 612 if (!mWebChromeClient.onJsAlert(mWebView, url, message, res)) { 613 new JsDialogHelper(res, JsDialogHelper.ALERT, null, message, url) 614 .showDialog(mWebView.getContext()); 615 } 616 } else { 617 receiver.cancel(); 618 } 619 TraceEvent.end(); 620 } 621 622 @Override 623 public void handleJsBeforeUnload(String url, String message, JsResultReceiver receiver) { 624 TraceEvent.begin(); 625 if (mWebChromeClient != null) { 626 final JsPromptResult res = 627 new JsPromptResultReceiverAdapter(receiver).getPromptResult(); 628 if (TRACE) Log.d(TAG, "onJsBeforeUnload"); 629 if (!mWebChromeClient.onJsBeforeUnload(mWebView, url, message, res)) { 630 new JsDialogHelper(res, JsDialogHelper.UNLOAD, null, message, url) 631 .showDialog(mWebView.getContext()); 632 } 633 } else { 634 receiver.cancel(); 635 } 636 TraceEvent.end(); 637 } 638 639 @Override 640 public void handleJsConfirm(String url, String message, JsResultReceiver receiver) { 641 TraceEvent.begin(); 642 if (mWebChromeClient != null) { 643 final JsPromptResult res = 644 new JsPromptResultReceiverAdapter(receiver).getPromptResult(); 645 if (TRACE) Log.d(TAG, "onJsConfirm"); 646 if (!mWebChromeClient.onJsConfirm(mWebView, url, message, res)) { 647 new JsDialogHelper(res, JsDialogHelper.CONFIRM, null, message, url) 648 .showDialog(mWebView.getContext()); 649 } 650 } else { 651 receiver.cancel(); 652 } 653 TraceEvent.end(); 654 } 655 656 @Override 657 public void handleJsPrompt(String url, String message, String defaultValue, 658 JsPromptResultReceiver receiver) { 659 TraceEvent.begin(); 660 if (mWebChromeClient != null) { 661 final JsPromptResult res = 662 new JsPromptResultReceiverAdapter(receiver).getPromptResult(); 663 if (TRACE) Log.d(TAG, "onJsPrompt"); 664 if (!mWebChromeClient.onJsPrompt(mWebView, url, message, defaultValue, res)) { 665 new JsDialogHelper(res, JsDialogHelper.PROMPT, defaultValue, message, url) 666 .showDialog(mWebView.getContext()); 667 } 668 } else { 669 receiver.cancel(); 670 } 671 TraceEvent.end(); 672 } 673 674 @Override 675 public void onReceivedHttpAuthRequest(AwHttpAuthHandler handler, String host, String realm) { 676 TraceEvent.begin(); 677 if (TRACE) Log.d(TAG, "onReceivedHttpAuthRequest=" + host); 678 mWebViewClient.onReceivedHttpAuthRequest(mWebView, 679 new AwHttpAuthHandlerAdapter(handler), host, realm); 680 TraceEvent.end(); 681 } 682 683 @Override 684 public void onReceivedSslError(final ValueCallback<Boolean> callback, SslError error) { 685 SslErrorHandler handler = new SslErrorHandler() { 686 @Override 687 public void proceed() { 688 postProceed(true); 689 } 690 @Override 691 public void cancel() { 692 postProceed(false); 693 } 694 private void postProceed(final boolean proceed) { 695 post(new Runnable() { 696 @Override 697 public void run() { 698 callback.onReceiveValue(proceed); 699 } 700 }); 701 } 702 }; 703 TraceEvent.begin(); 704 if (TRACE) Log.d(TAG, "onReceivedSslError"); 705 mWebViewClient.onReceivedSslError(mWebView, handler, error); 706 TraceEvent.end(); 707 } 708 709 @Override 710 public void onReceivedLoginRequest(String realm, String account, String args) { 711 TraceEvent.begin(); 712 if (TRACE) Log.d(TAG, "onReceivedLoginRequest=" + realm); 713 mWebViewClient.onReceivedLoginRequest(mWebView, realm, account, args); 714 TraceEvent.end(); 715 } 716 717 @Override 718 public void onFormResubmission(Message dontResend, Message resend) { 719 TraceEvent.begin(); 720 if (TRACE) Log.d(TAG, "onFormResubmission"); 721 mWebViewClient.onFormResubmission(mWebView, dontResend, resend); 722 TraceEvent.end(); 723 } 724 725 @Override 726 public void onDownloadStart(String url, 727 String userAgent, 728 String contentDisposition, 729 String mimeType, 730 long contentLength) { 731 if (mDownloadListener != null) { 732 TraceEvent.begin(); 733 if (TRACE) Log.d(TAG, "onDownloadStart"); 734 mDownloadListener.onDownloadStart(url, 735 userAgent, 736 contentDisposition, 737 mimeType, 738 contentLength); 739 TraceEvent.end(); 740 } 741 } 742 743 @Override 744 public void showFileChooser(final ValueCallback<String[]> uploadFileCallback, 745 final AwContentsClient.FileChooserParams fileChooserParams) { 746 if (mWebChromeClient == null) { 747 uploadFileCallback.onReceiveValue(null); 748 return; 749 } 750 TraceEvent.begin(); 751 /* 752 // TODO: Call new API here when it's added in frameworks/base - see http://ag/335990 753 WebChromeClient.FileChooserParams p = new WebChromeClient.FileChooserParams(); 754 p.mode = fileChooserParams.mode; 755 p.acceptTypes = fileChooserParams.acceptTypes; 756 p.title = fileChooserParams.title; 757 p.defaultFilename = fileChooserParams.defaultFilename; 758 p.capture = fileChooserParams.capture; 759 if (TRACE) Log.d(TAG, "showFileChooser"); 760 if (Build.VERSION.SDK_INT > VERSION_CODES.KIT_KAT && 761 !mWebChromeClient.showFileChooser(mWebView, uploadFileCallback, p)) { 762 return; 763 } 764 if (mWebView.getContext().getApplicationInfo().targetSdkVersion > 765 Build.VERSION_CODES.KIT_KAT) { 766 uploadFileCallback.onReceiveValue(null); 767 return; 768 } 769 */ 770 ValueCallback<Uri> innerCallback = new ValueCallback<Uri>() { 771 final AtomicBoolean completed = new AtomicBoolean(false); 772 @Override 773 public void onReceiveValue(Uri uri) { 774 if (completed.getAndSet(true)) return; 775 uploadFileCallback.onReceiveValue( 776 uri == null ? null : new String[] { uri.toString() }); 777 } 778 }; 779 if (TRACE) Log.d(TAG, "openFileChooser"); 780 mWebChromeClient.openFileChooser(innerCallback, fileChooserParams.acceptTypes, 781 fileChooserParams.capture ? "*" : ""); 782 TraceEvent.end(); 783 } 784 785 @Override 786 public void onScaleChangedScaled(float oldScale, float newScale) { 787 TraceEvent.begin(); 788 if (TRACE) Log.d(TAG, " onScaleChangedScaled"); 789 mWebViewClient.onScaleChanged(mWebView, oldScale, newScale); 790 TraceEvent.end(); 791 } 792 793 @Override 794 public void onShowCustomView(View view, CustomViewCallback cb) { 795 TraceEvent.begin(); 796 if (mWebChromeClient != null) { 797 if (TRACE) Log.d(TAG, "onShowCustomView"); 798 mWebChromeClient.onShowCustomView(view, cb); 799 } 800 TraceEvent.end(); 801 } 802 803 @Override 804 public void onHideCustomView() { 805 TraceEvent.begin(); 806 if (mWebChromeClient != null) { 807 if (TRACE) Log.d(TAG, "onHideCustomView"); 808 mWebChromeClient.onHideCustomView(); 809 } 810 TraceEvent.end(); 811 } 812 813 @Override 814 protected View getVideoLoadingProgressView() { 815 TraceEvent.begin(); 816 View result; 817 if (mWebChromeClient != null) { 818 if (TRACE) Log.d(TAG, "getVideoLoadingProgressView"); 819 result = mWebChromeClient.getVideoLoadingProgressView(); 820 } else { 821 result = null; 822 } 823 TraceEvent.end(); 824 return result; 825 } 826 827 @Override 828 public Bitmap getDefaultVideoPoster() { 829 TraceEvent.begin(); 830 Bitmap result = null; 831 if (mWebChromeClient != null) { 832 if (TRACE) Log.d(TAG, "getDefaultVideoPoster"); 833 result = mWebChromeClient.getDefaultVideoPoster(); 834 } 835 if (result == null) { 836 // The ic_media_video_poster icon is transparent so we need to draw it on a gray 837 // background. 838 Bitmap poster = BitmapFactory.decodeResource( 839 mWebView.getContext().getResources(), 840 com.android.internal.R.drawable.ic_media_video_poster); 841 result = Bitmap.createBitmap(poster.getWidth(), poster.getHeight(), poster.getConfig()); 842 result.eraseColor(Color.GRAY); 843 Canvas canvas = new Canvas(result); 844 canvas.drawBitmap(poster, 0f, 0f, null); 845 } 846 TraceEvent.end(); 847 return result; 848 } 849 850 private static class AwHttpAuthHandlerAdapter extends android.webkit.HttpAuthHandler { 851 private AwHttpAuthHandler mAwHandler; 852 853 public AwHttpAuthHandlerAdapter(AwHttpAuthHandler awHandler) { 854 mAwHandler = awHandler; 855 } 856 857 @Override 858 public void proceed(String username, String password) { 859 if (username == null) { 860 username = ""; 861 } 862 863 if (password == null) { 864 password = ""; 865 } 866 mAwHandler.proceed(username, password); 867 } 868 869 @Override 870 public void cancel() { 871 mAwHandler.cancel(); 872 } 873 874 @Override 875 public boolean useHttpAuthUsernamePassword() { 876 return mAwHandler.isFirstAttempt(); 877 } 878 } 879} 880