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.ClientCertRequest; 39import android.webkit.ConsoleMessage; 40import android.webkit.DownloadListener; 41import android.webkit.GeolocationPermissions; 42import android.webkit.JsDialogHelper; 43import android.webkit.JsPromptResult; 44import android.webkit.JsResult; 45import android.webkit.PermissionRequest; 46import android.webkit.SslErrorHandler; 47import android.webkit.ValueCallback; 48import android.webkit.WebChromeClient; 49import android.webkit.WebChromeClient.CustomViewCallback; 50import android.webkit.WebResourceResponse; 51import android.webkit.WebResourceRequest; 52import android.webkit.WebView; 53import android.webkit.WebViewClient; 54 55import org.chromium.android_webview.AwContentsClient; 56import org.chromium.android_webview.AwContentsClientBridge; 57import org.chromium.android_webview.AwHttpAuthHandler; 58import org.chromium.android_webview.AwWebResourceResponse; 59import org.chromium.android_webview.JsPromptResultReceiver; 60import org.chromium.android_webview.JsResultReceiver; 61import org.chromium.android_webview.permission.AwPermissionRequest; 62import org.chromium.base.ThreadUtils; 63import org.chromium.base.TraceEvent; 64import org.chromium.content.browser.ContentView; 65import org.chromium.content.browser.ContentViewClient; 66 67import java.lang.ref.WeakReference; 68import java.net.URISyntaxException; 69import java.security.Principal; 70import java.security.PrivateKey; 71import java.security.cert.X509Certificate; 72import java.util.ArrayList; 73import java.util.HashMap; 74import java.util.Map; 75import java.util.WeakHashMap; 76 77/** 78 * An adapter class that forwards the callbacks from {@link ContentViewClient} 79 * to the appropriate {@link WebViewClient} or {@link WebChromeClient}. 80 * 81 * An instance of this class is associated with one {@link WebViewChromium} 82 * instance. A WebViewChromium is a WebView implementation provider (that is 83 * android.webkit.WebView delegates all functionality to it) and has exactly 84 * one corresponding {@link ContentView} instance. 85 * 86 * A {@link ContentViewClient} may be shared between multiple {@link ContentView}s, 87 * and hence multiple WebViews. Many WebViewClient methods pass the source 88 * WebView as an argument. This means that we either need to pass the 89 * corresponding ContentView to the corresponding ContentViewClient methods, 90 * or use an instance of ContentViewClientAdapter per WebViewChromium, to 91 * allow the source WebView to be injected by ContentViewClientAdapter. We 92 * choose the latter, because it makes for a cleaner design. 93 */ 94public class WebViewContentsClientAdapter extends AwContentsClient { 95 // TAG is chosen for consistency with classic webview tracing. 96 private static final String TAG = "WebViewCallback"; 97 // Enables API callback tracing 98 private static final boolean TRACE = android.webkit.DebugFlags.TRACE_CALLBACK; 99 // The WebView instance that this adapter is serving. 100 private final WebView mWebView; 101 // The WebViewClient instance that was passed to WebView.setWebViewClient(). 102 private WebViewClient mWebViewClient; 103 // The WebChromeClient instance that was passed to WebView.setContentViewClient(). 104 private WebChromeClient mWebChromeClient; 105 // The listener receiving find-in-page API results. 106 private WebView.FindListener mFindListener; 107 // The listener receiving notifications of screen updates. 108 private WebView.PictureListener mPictureListener; 109 110 private DownloadListener mDownloadListener; 111 112 private Handler mUiThreadHandler; 113 114 private static final int NEW_WEBVIEW_CREATED = 100; 115 116 private WeakHashMap<AwPermissionRequest, WeakReference<PermissionRequestAdapter>> 117 mOngoingPermissionRequests; 118 /** 119 * Adapter constructor. 120 * 121 * @param webView the {@link WebView} instance that this adapter is serving. 122 */ 123 WebViewContentsClientAdapter(WebView webView) { 124 if (webView == null) { 125 throw new IllegalArgumentException("webView can't be null"); 126 } 127 128 mWebView = webView; 129 setWebViewClient(null); 130 131 mUiThreadHandler = new Handler() { 132 133 @Override 134 public void handleMessage(Message msg) { 135 switch(msg.what) { 136 case NEW_WEBVIEW_CREATED: 137 WebView.WebViewTransport t = (WebView.WebViewTransport) msg.obj; 138 WebView newWebView = t.getWebView(); 139 if (newWebView == mWebView) { 140 throw new IllegalArgumentException( 141 "Parent WebView cannot host it's own popup window. Please " + 142 "use WebSettings.setSupportMultipleWindows(false)"); 143 } 144 145 if (newWebView != null && newWebView.copyBackForwardList().getSize() != 0) { 146 throw new IllegalArgumentException( 147 "New WebView for popup window must not have been previously " + 148 "navigated."); 149 } 150 151 WebViewChromium.completeWindowCreation(mWebView, newWebView); 152 break; 153 default: 154 throw new IllegalStateException(); 155 } 156 } 157 }; 158 159 } 160 161 // WebViewClassic is coded in such a way that even if a null WebViewClient is set, 162 // certain actions take place. 163 // We choose to replicate this behavior by using a NullWebViewClient implementation (also known 164 // as the Null Object pattern) rather than duplicating the WebViewClassic approach in 165 // ContentView. 166 static class NullWebViewClient extends WebViewClient { 167 @Override 168 public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) { 169 // TODO: Investigate more and add a test case. 170 // This is reflecting Clank's behavior. 171 int keyCode = event.getKeyCode(); 172 return !ContentViewClient.shouldPropagateKey(keyCode); 173 } 174 175 @Override 176 public boolean shouldOverrideUrlLoading(WebView view, String url) { 177 Intent intent; 178 // Perform generic parsing of the URI to turn it into an Intent. 179 try { 180 intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME); 181 } catch (URISyntaxException ex) { 182 Log.w(TAG, "Bad URI " + url + ": " + ex.getMessage()); 183 return false; 184 } 185 // Sanitize the Intent, ensuring web pages can not bypass browser 186 // security (only access to BROWSABLE activities). 187 intent.addCategory(Intent.CATEGORY_BROWSABLE); 188 intent.setComponent(null); 189 Intent selector = intent.getSelector(); 190 if (selector != null) { 191 selector.addCategory(Intent.CATEGORY_BROWSABLE); 192 selector.setComponent(null); 193 } 194 // Pass the package name as application ID so that the intent from the 195 // same application can be opened in the same tab. 196 intent.putExtra(Browser.EXTRA_APPLICATION_ID, 197 view.getContext().getPackageName()); 198 try { 199 view.getContext().startActivity(intent); 200 } catch (ActivityNotFoundException ex) { 201 Log.w(TAG, "No application can handle " + url); 202 return false; 203 } 204 return true; 205 } 206 } 207 208 void setWebViewClient(WebViewClient client) { 209 if (client != null) { 210 mWebViewClient = client; 211 } else { 212 mWebViewClient = new NullWebViewClient(); 213 } 214 } 215 216 void setWebChromeClient(WebChromeClient client) { 217 mWebChromeClient = client; 218 } 219 220 void setDownloadListener(DownloadListener listener) { 221 mDownloadListener = listener; 222 } 223 224 void setFindListener(WebView.FindListener listener) { 225 mFindListener = listener; 226 } 227 228 void setPictureListener(WebView.PictureListener listener) { 229 mPictureListener = listener; 230 } 231 232 //-------------------------------------------------------------------------------------------- 233 // Adapter for all the methods. 234 //-------------------------------------------------------------------------------------------- 235 236 /** 237 * @see AwContentsClient#getVisitedHistory 238 */ 239 @Override 240 public void getVisitedHistory(ValueCallback<String[]> callback) { 241 TraceEvent.begin(); 242 if (mWebChromeClient != null) { 243 if (TRACE) Log.d(TAG, "getVisitedHistory"); 244 mWebChromeClient.getVisitedHistory(callback); 245 } 246 TraceEvent.end(); 247 } 248 249 /** 250 * @see AwContentsClient#doUpdateVisiteHistory(String, boolean) 251 */ 252 @Override 253 public void doUpdateVisitedHistory(String url, boolean isReload) { 254 TraceEvent.begin(); 255 if (TRACE) Log.d(TAG, "doUpdateVisitedHistory=" + url + " reload=" + isReload); 256 mWebViewClient.doUpdateVisitedHistory(mWebView, url, isReload); 257 TraceEvent.end(); 258 } 259 260 /** 261 * @see AwContentsClient#onProgressChanged(int) 262 */ 263 @Override 264 public void onProgressChanged(int progress) { 265 TraceEvent.begin(); 266 if (mWebChromeClient != null) { 267 if (TRACE) Log.d(TAG, "onProgressChanged=" + progress); 268 mWebChromeClient.onProgressChanged(mWebView, progress); 269 } 270 TraceEvent.end(); 271 } 272 273 private static class WebResourceRequestImpl implements WebResourceRequest { 274 private final ShouldInterceptRequestParams mParams; 275 276 public WebResourceRequestImpl(ShouldInterceptRequestParams params) { 277 mParams = params; 278 } 279 280 @Override 281 public Uri getUrl() { 282 return Uri.parse(mParams.url); 283 } 284 285 @Override 286 public boolean isForMainFrame() { 287 return mParams.isMainFrame; 288 } 289 290 @Override 291 public boolean hasGesture() { 292 return mParams.hasUserGesture; 293 } 294 295 @Override 296 public String getMethod() { 297 return mParams.method; 298 } 299 300 @Override 301 public Map<String, String> getRequestHeaders() { 302 return mParams.requestHeaders; 303 } 304 } 305 306 /** 307 * @see AwContentsClient#shouldInterceptRequest(java.lang.String) 308 */ 309 @Override 310 public AwWebResourceResponse shouldInterceptRequest(ShouldInterceptRequestParams params) { 311 TraceEvent.begin(); 312 if (TRACE) Log.d(TAG, "shouldInterceptRequest=" + params.url); 313 WebResourceResponse response = mWebViewClient.shouldInterceptRequest(mWebView, 314 new WebResourceRequestImpl(params)); 315 TraceEvent.end(); 316 if (response == null) return null; 317 318 // AwWebResourceResponse should support null headers. b/16332774. 319 Map<String, String> responseHeaders = response.getResponseHeaders(); 320 if (responseHeaders == null) 321 responseHeaders = new HashMap<String, String>(); 322 323 return new AwWebResourceResponse( 324 response.getMimeType(), 325 response.getEncoding(), 326 response.getData(), 327 response.getStatusCode(), 328 response.getReasonPhrase(), 329 responseHeaders); 330 } 331 332 /** 333 * @see AwContentsClient#shouldOverrideUrlLoading(java.lang.String) 334 */ 335 @Override 336 public boolean shouldOverrideUrlLoading(String url) { 337 TraceEvent.begin(); 338 if (TRACE) Log.d(TAG, "shouldOverrideUrlLoading=" + url); 339 boolean result = mWebViewClient.shouldOverrideUrlLoading(mWebView, url); 340 TraceEvent.end(); 341 return result; 342 } 343 344 /** 345 * @see AwContentsClient#onUnhandledKeyEvent(android.view.KeyEvent) 346 */ 347 @Override 348 public void onUnhandledKeyEvent(KeyEvent event) { 349 TraceEvent.begin(); 350 if (TRACE) Log.d(TAG, "onUnhandledKeyEvent"); 351 mWebViewClient.onUnhandledKeyEvent(mWebView, event); 352 TraceEvent.end(); 353 } 354 355 /** 356 * @see AwContentsClient#onConsoleMessage(android.webkit.ConsoleMessage) 357 */ 358 @Override 359 public boolean onConsoleMessage(ConsoleMessage consoleMessage) { 360 TraceEvent.begin(); 361 boolean result; 362 if (mWebChromeClient != null) { 363 if (TRACE) Log.d(TAG, "onConsoleMessage: " + consoleMessage.message()); 364 result = mWebChromeClient.onConsoleMessage(consoleMessage); 365 String message = consoleMessage.message(); 366 if (result && message != null && message.startsWith("[blocked]")) { 367 Log.e(TAG, "Blocked URL: " + message); 368 } 369 } else { 370 result = false; 371 } 372 TraceEvent.end(); 373 return result; 374 } 375 376 /** 377 * @see AwContentsClient#onFindResultReceived(int,int,boolean) 378 */ 379 @Override 380 public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, 381 boolean isDoneCounting) { 382 if (mFindListener == null) return; 383 TraceEvent.begin(); 384 if (TRACE) Log.d(TAG, "onFindResultReceived"); 385 mFindListener.onFindResultReceived(activeMatchOrdinal, numberOfMatches, isDoneCounting); 386 TraceEvent.end(); 387 } 388 389 /** 390 * @See AwContentsClient#onNewPicture(Picture) 391 */ 392 @Override 393 public void onNewPicture(Picture picture) { 394 if (mPictureListener == null) return; 395 TraceEvent.begin(); 396 if (TRACE) Log.d(TAG, "onNewPicture"); 397 mPictureListener.onNewPicture(mWebView, picture); 398 TraceEvent.end(); 399 } 400 401 @Override 402 public void onLoadResource(String url) { 403 TraceEvent.begin(); 404 if (TRACE) Log.d(TAG, "onLoadResource=" + url); 405 mWebViewClient.onLoadResource(mWebView, url); 406 TraceEvent.end(); 407 } 408 409 @Override 410 public boolean onCreateWindow(boolean isDialog, boolean isUserGesture) { 411 Message m = mUiThreadHandler.obtainMessage( 412 NEW_WEBVIEW_CREATED, mWebView.new WebViewTransport()); 413 TraceEvent.begin(); 414 boolean result; 415 if (mWebChromeClient != null) { 416 if (TRACE) Log.d(TAG, "onCreateWindow"); 417 result = mWebChromeClient.onCreateWindow(mWebView, isDialog, isUserGesture, m); 418 } else { 419 result = false; 420 } 421 TraceEvent.end(); 422 return result; 423 } 424 425 /** 426 * @see AwContentsClient#onCloseWindow() 427 */ 428 @Override 429 public void onCloseWindow() { 430 TraceEvent.begin(); 431 if (mWebChromeClient != null) { 432 if (TRACE) Log.d(TAG, "onCloseWindow"); 433 mWebChromeClient.onCloseWindow(mWebView); 434 } 435 TraceEvent.end(); 436 } 437 438 /** 439 * @see AwContentsClient#onRequestFocus() 440 */ 441 @Override 442 public void onRequestFocus() { 443 TraceEvent.begin(); 444 if (mWebChromeClient != null) { 445 if (TRACE) Log.d(TAG, "onRequestFocus"); 446 mWebChromeClient.onRequestFocus(mWebView); 447 } 448 TraceEvent.end(); 449 } 450 451 /** 452 * @see AwContentsClient#onReceivedTouchIconUrl(String url, boolean precomposed) 453 */ 454 @Override 455 public void onReceivedTouchIconUrl(String url, boolean precomposed) { 456 TraceEvent.begin(); 457 if (mWebChromeClient != null) { 458 if (TRACE) Log.d(TAG, "onReceivedTouchIconUrl=" + url); 459 mWebChromeClient.onReceivedTouchIconUrl(mWebView, url, precomposed); 460 } 461 TraceEvent.end(); 462 } 463 464 /** 465 * @see AwContentsClient#onReceivedIcon(Bitmap bitmap) 466 */ 467 @Override 468 public void onReceivedIcon(Bitmap bitmap) { 469 TraceEvent.begin(); 470 if (mWebChromeClient != null) { 471 if (TRACE) Log.d(TAG, "onReceivedIcon"); 472 mWebChromeClient.onReceivedIcon(mWebView, bitmap); 473 } 474 TraceEvent.end(); 475 } 476 477 /** 478 * @see ContentViewClient#onPageStarted(String) 479 */ 480 @Override 481 public void onPageStarted(String url) { 482 TraceEvent.begin(); 483 if (TRACE) Log.d(TAG, "onPageStarted=" + url); 484 mWebViewClient.onPageStarted(mWebView, url, mWebView.getFavicon()); 485 TraceEvent.end(); 486 } 487 488 /** 489 * @see ContentViewClient#onPageFinished(String) 490 */ 491 @Override 492 public void onPageFinished(String url) { 493 TraceEvent.begin(); 494 if (TRACE) Log.d(TAG, "onPageFinished=" + url); 495 mWebViewClient.onPageFinished(mWebView, url); 496 TraceEvent.end(); 497 498 // See b/8208948 499 // This fakes an onNewPicture callback after onPageFinished to allow 500 // CTS tests to run in an un-flaky manner. This is required as the 501 // path for sending Picture updates in Chromium are decoupled from the 502 // page loading callbacks, i.e. the Chrome compositor may draw our 503 // content and send the Picture before onPageStarted or onPageFinished 504 // are invoked. The CTS harness discards any pictures it receives before 505 // onPageStarted is invoked, so in the case we get the Picture before that and 506 // no further updates after onPageStarted, we'll fail the test by timing 507 // out waiting for a Picture. 508 // To ensure backwards compatibility, we need to defer sending Picture updates 509 // until onPageFinished has been invoked. This work is being done 510 // upstream, and we can revert this hack when it lands. 511 if (mPictureListener != null) { 512 ThreadUtils.postOnUiThreadDelayed(new Runnable() { 513 @Override 514 public void run() { 515 UnimplementedWebViewApi.invoke(); 516 if (mPictureListener != null) { 517 if (TRACE) Log.d(TAG, "onPageFinished-fake"); 518 mPictureListener.onNewPicture(mWebView, new Picture()); 519 } 520 } 521 }, 100); 522 } 523 } 524 525 /** 526 * @see ContentViewClient#onReceivedError(int,String,String) 527 */ 528 @Override 529 public void onReceivedError(int errorCode, String description, String failingUrl) { 530 if (description == null || description.isEmpty()) { 531 // ErrorStrings is @hidden, so we can't do this in AwContents. 532 // Normally the net/ layer will set a valid description, but for synthesized callbacks 533 // (like in the case for intercepted requests) AwContents will pass in null. 534 description = ErrorStrings.getString(errorCode, mWebView.getContext()); 535 } 536 TraceEvent.begin(); 537 if (TRACE) Log.d(TAG, "onReceivedError=" + failingUrl); 538 mWebViewClient.onReceivedError(mWebView, errorCode, description, failingUrl); 539 TraceEvent.end(); 540 } 541 542 /** 543 * @see ContentViewClient#onReceivedTitle(String) 544 */ 545 @Override 546 public void onReceivedTitle(String title) { 547 TraceEvent.begin(); 548 if (mWebChromeClient != null) { 549 if (TRACE) Log.d(TAG, "onReceivedTitle"); 550 mWebChromeClient.onReceivedTitle(mWebView, title); 551 } 552 TraceEvent.end(); 553 } 554 555 556 /** 557 * @see ContentViewClient#shouldOverrideKeyEvent(KeyEvent) 558 */ 559 @Override 560 public boolean shouldOverrideKeyEvent(KeyEvent event) { 561 // The check below is reflecting Clank's behavior and is a workaround for http://b/7697782. 562 // 1. The check for system key should be made in AwContents or ContentViewCore, before 563 // shouldOverrideKeyEvent() is called at all. 564 // 2. shouldOverrideKeyEvent() should be called in onKeyDown/onKeyUp, not from 565 // dispatchKeyEvent(). 566 if (!ContentViewClient.shouldPropagateKey(event.getKeyCode())) return true; 567 TraceEvent.begin(); 568 if (TRACE) Log.d(TAG, "shouldOverrideKeyEvent"); 569 boolean result = mWebViewClient.shouldOverrideKeyEvent(mWebView, event); 570 TraceEvent.end(); 571 return result; 572 } 573 574 575 /** 576 * @see ContentViewClient#onStartContentIntent(Context, String) 577 * Callback when detecting a click on a content link. 578 */ 579 // TODO: Delete this method when removed from base class. 580 public void onStartContentIntent(Context context, String contentUrl) { 581 TraceEvent.begin(); 582 if (TRACE) Log.d(TAG, "shouldOverrideUrlLoading=" + contentUrl); 583 mWebViewClient.shouldOverrideUrlLoading(mWebView, contentUrl); 584 TraceEvent.end(); 585 } 586 587 @Override 588 public void onGeolocationPermissionsShowPrompt(String origin, 589 GeolocationPermissions.Callback callback) { 590 TraceEvent.begin(); 591 if (mWebChromeClient != null) { 592 if (TRACE) Log.d(TAG, "onGeolocationPermissionsShowPrompt"); 593 mWebChromeClient.onGeolocationPermissionsShowPrompt(origin, callback); 594 } 595 TraceEvent.end(); 596 } 597 598 @Override 599 public void onGeolocationPermissionsHidePrompt() { 600 TraceEvent.begin(); 601 if (mWebChromeClient != null) { 602 if (TRACE) Log.d(TAG, "onGeolocationPermissionsHidePrompt"); 603 mWebChromeClient.onGeolocationPermissionsHidePrompt(); 604 } 605 TraceEvent.end(); 606 } 607 608 @Override 609 public void onPermissionRequest(AwPermissionRequest permissionRequest) { 610 TraceEvent.begin(); 611 if (mWebChromeClient != null) { 612 if (TRACE) Log.d(TAG, "onPermissionRequest"); 613 if (mOngoingPermissionRequests == null) { 614 mOngoingPermissionRequests = 615 new WeakHashMap<AwPermissionRequest, WeakReference<PermissionRequestAdapter>>(); 616 } 617 PermissionRequestAdapter adapter = new PermissionRequestAdapter(permissionRequest); 618 mOngoingPermissionRequests.put( 619 permissionRequest, new WeakReference<PermissionRequestAdapter>(adapter)); 620 mWebChromeClient.onPermissionRequest(adapter); 621 } else { 622 // By default, we deny the permission. 623 permissionRequest.deny(); 624 } 625 TraceEvent.end(); 626 } 627 628 @Override 629 public void onPermissionRequestCanceled(AwPermissionRequest permissionRequest) { 630 TraceEvent.begin(); 631 if (mWebChromeClient != null && mOngoingPermissionRequests != null) { 632 if (TRACE) Log.d(TAG, "onPermissionRequestCanceled"); 633 WeakReference<PermissionRequestAdapter> weakRef = 634 mOngoingPermissionRequests.get(permissionRequest); 635 // We don't hold strong reference to PermissionRequestAdpater and don't expect the 636 // user only holds weak reference to it either, if so, user has no way to call 637 // grant()/deny(), and no need to be notified the cancellation of request. 638 if (weakRef != null) { 639 PermissionRequestAdapter adapter = weakRef.get(); 640 if (adapter != null) mWebChromeClient.onPermissionRequestCanceled(adapter); 641 } 642 } 643 TraceEvent.end(); 644 } 645 646 private static class JsPromptResultReceiverAdapter implements JsResult.ResultReceiver { 647 private JsPromptResultReceiver mChromePromptResultReceiver; 648 private JsResultReceiver mChromeResultReceiver; 649 // We hold onto the JsPromptResult here, just to avoid the need to downcast 650 // in onJsResultComplete. 651 private final JsPromptResult mPromptResult = new JsPromptResult(this); 652 653 public JsPromptResultReceiverAdapter(JsPromptResultReceiver receiver) { 654 mChromePromptResultReceiver = receiver; 655 } 656 657 public JsPromptResultReceiverAdapter(JsResultReceiver receiver) { 658 mChromeResultReceiver = receiver; 659 } 660 661 public JsPromptResult getPromptResult() { 662 return mPromptResult; 663 } 664 665 @Override 666 public void onJsResultComplete(JsResult result) { 667 if (mChromePromptResultReceiver != null) { 668 if (mPromptResult.getResult()) { 669 mChromePromptResultReceiver.confirm(mPromptResult.getStringResult()); 670 } else { 671 mChromePromptResultReceiver.cancel(); 672 } 673 } else { 674 if (mPromptResult.getResult()) { 675 mChromeResultReceiver.confirm(); 676 } else { 677 mChromeResultReceiver.cancel(); 678 } 679 } 680 } 681 } 682 683 @Override 684 public void handleJsAlert(String url, String message, JsResultReceiver receiver) { 685 TraceEvent.begin(); 686 if (mWebChromeClient != null) { 687 final JsPromptResult res = 688 new JsPromptResultReceiverAdapter(receiver).getPromptResult(); 689 if (TRACE) Log.d(TAG, "onJsAlert"); 690 if (!mWebChromeClient.onJsAlert(mWebView, url, message, res)) { 691 new JsDialogHelper(res, JsDialogHelper.ALERT, null, message, url) 692 .showDialog(mWebView.getContext()); 693 } 694 } else { 695 receiver.cancel(); 696 } 697 TraceEvent.end(); 698 } 699 700 @Override 701 public void handleJsBeforeUnload(String url, String message, JsResultReceiver receiver) { 702 TraceEvent.begin(); 703 if (mWebChromeClient != null) { 704 final JsPromptResult res = 705 new JsPromptResultReceiverAdapter(receiver).getPromptResult(); 706 if (TRACE) Log.d(TAG, "onJsBeforeUnload"); 707 if (!mWebChromeClient.onJsBeforeUnload(mWebView, url, message, res)) { 708 new JsDialogHelper(res, JsDialogHelper.UNLOAD, null, message, url) 709 .showDialog(mWebView.getContext()); 710 } 711 } else { 712 receiver.cancel(); 713 } 714 TraceEvent.end(); 715 } 716 717 @Override 718 public void handleJsConfirm(String url, String message, JsResultReceiver receiver) { 719 TraceEvent.begin(); 720 if (mWebChromeClient != null) { 721 final JsPromptResult res = 722 new JsPromptResultReceiverAdapter(receiver).getPromptResult(); 723 if (TRACE) Log.d(TAG, "onJsConfirm"); 724 if (!mWebChromeClient.onJsConfirm(mWebView, url, message, res)) { 725 new JsDialogHelper(res, JsDialogHelper.CONFIRM, null, message, url) 726 .showDialog(mWebView.getContext()); 727 } 728 } else { 729 receiver.cancel(); 730 } 731 TraceEvent.end(); 732 } 733 734 @Override 735 public void handleJsPrompt(String url, String message, String defaultValue, 736 JsPromptResultReceiver receiver) { 737 TraceEvent.begin(); 738 if (mWebChromeClient != null) { 739 final JsPromptResult res = 740 new JsPromptResultReceiverAdapter(receiver).getPromptResult(); 741 if (TRACE) Log.d(TAG, "onJsPrompt"); 742 if (!mWebChromeClient.onJsPrompt(mWebView, url, message, defaultValue, res)) { 743 new JsDialogHelper(res, JsDialogHelper.PROMPT, defaultValue, message, url) 744 .showDialog(mWebView.getContext()); 745 } 746 } else { 747 receiver.cancel(); 748 } 749 TraceEvent.end(); 750 } 751 752 @Override 753 public void onReceivedHttpAuthRequest(AwHttpAuthHandler handler, String host, String realm) { 754 TraceEvent.begin(); 755 if (TRACE) Log.d(TAG, "onReceivedHttpAuthRequest=" + host); 756 mWebViewClient.onReceivedHttpAuthRequest(mWebView, 757 new AwHttpAuthHandlerAdapter(handler), host, realm); 758 TraceEvent.end(); 759 } 760 761 @Override 762 public void onReceivedSslError(final ValueCallback<Boolean> callback, SslError error) { 763 SslErrorHandler handler = new SslErrorHandler() { 764 @Override 765 public void proceed() { 766 callback.onReceiveValue(true); 767 } 768 @Override 769 public void cancel() { 770 callback.onReceiveValue(false); 771 } 772 }; 773 TraceEvent.begin(); 774 if (TRACE) Log.d(TAG, "onReceivedSslError"); 775 mWebViewClient.onReceivedSslError(mWebView, handler, error); 776 TraceEvent.end(); 777 } 778 779 private static class ClientCertRequestImpl extends ClientCertRequest { 780 781 final private AwContentsClientBridge.ClientCertificateRequestCallback mCallback; 782 final private String[] mKeyTypes; 783 final private Principal[] mPrincipals; 784 final private String mHost; 785 final private int mPort; 786 787 public ClientCertRequestImpl( 788 AwContentsClientBridge.ClientCertificateRequestCallback callback, 789 String[] keyTypes, Principal[] principals, String host, int port) { 790 mCallback = callback; 791 mKeyTypes = keyTypes; 792 mPrincipals = principals; 793 mHost = host; 794 mPort = port; 795 } 796 797 @Override 798 public String[] getKeyTypes() { 799 // This is already a copy of native argument, so return directly. 800 return mKeyTypes; 801 } 802 803 @Override 804 public Principal[] getPrincipals() { 805 // This is already a copy of native argument, so return directly. 806 return mPrincipals; 807 } 808 809 @Override 810 public String getHost() { 811 return mHost; 812 } 813 814 @Override 815 public int getPort() { 816 return mPort; 817 } 818 819 @Override 820 public void proceed(final PrivateKey privateKey, final X509Certificate[] chain) { 821 mCallback.proceed(privateKey, chain); 822 } 823 824 @Override 825 public void ignore() { 826 mCallback.ignore(); 827 } 828 829 @Override 830 public void cancel() { 831 mCallback.cancel(); 832 } 833 } 834 835 @Override 836 public void onReceivedClientCertRequest( 837 AwContentsClientBridge.ClientCertificateRequestCallback callback, 838 String[] keyTypes, Principal[] principals, String host, int port) { 839 if (TRACE) Log.d(TAG, "onReceivedClientCertRequest"); 840 TraceEvent.begin(); 841 final ClientCertRequestImpl request = new ClientCertRequestImpl(callback, 842 keyTypes, principals, host, port); 843 mWebViewClient.onReceivedClientCertRequest(mWebView, request); 844 TraceEvent.end(); 845 } 846 847 @Override 848 public void onReceivedLoginRequest(String realm, String account, String args) { 849 TraceEvent.begin(); 850 if (TRACE) Log.d(TAG, "onReceivedLoginRequest=" + realm); 851 mWebViewClient.onReceivedLoginRequest(mWebView, realm, account, args); 852 TraceEvent.end(); 853 } 854 855 @Override 856 public void onFormResubmission(Message dontResend, Message resend) { 857 TraceEvent.begin(); 858 if (TRACE) Log.d(TAG, "onFormResubmission"); 859 mWebViewClient.onFormResubmission(mWebView, dontResend, resend); 860 TraceEvent.end(); 861 } 862 863 @Override 864 public void onDownloadStart(String url, 865 String userAgent, 866 String contentDisposition, 867 String mimeType, 868 long contentLength) { 869 if (mDownloadListener != null) { 870 TraceEvent.begin(); 871 if (TRACE) Log.d(TAG, "onDownloadStart"); 872 mDownloadListener.onDownloadStart(url, 873 userAgent, 874 contentDisposition, 875 mimeType, 876 contentLength); 877 TraceEvent.end(); 878 } 879 } 880 881 @Override 882 public void showFileChooser(final ValueCallback<String[]> uploadFileCallback, 883 final AwContentsClient.FileChooserParams fileChooserParams) { 884 if (mWebChromeClient == null) { 885 uploadFileCallback.onReceiveValue(null); 886 return; 887 } 888 TraceEvent.begin(); 889 FileChooserParamsAdapter adapter = new FileChooserParamsAdapter( 890 fileChooserParams, mWebView.getContext()); 891 if (TRACE) Log.d(TAG, "showFileChooser"); 892 ValueCallback<Uri[]> callbackAdapter = new ValueCallback<Uri[]>() { 893 private boolean mCompleted; 894 @Override 895 public void onReceiveValue(Uri[] uriList) { 896 if (mCompleted) { 897 throw new IllegalStateException("showFileChooser result was already called"); 898 } 899 mCompleted = true; 900 String s[] = null; 901 if (uriList != null) { 902 s = new String[uriList.length]; 903 for(int i = 0; i < uriList.length; i++) { 904 s[i] = uriList[i].toString(); 905 } 906 } 907 uploadFileCallback.onReceiveValue(s); 908 } 909 }; 910 if (mWebChromeClient.onShowFileChooser(mWebView, callbackAdapter, adapter)) { 911 return; 912 } 913 if (mWebView.getContext().getApplicationInfo().targetSdkVersion > 914 Build.VERSION_CODES.KITKAT) { 915 uploadFileCallback.onReceiveValue(null); 916 return; 917 } 918 ValueCallback<Uri> innerCallback = new ValueCallback<Uri>() { 919 private boolean mCompleted; 920 @Override 921 public void onReceiveValue(Uri uri) { 922 if (mCompleted) { 923 throw new IllegalStateException("showFileChooser result was already called"); 924 } 925 mCompleted = true; 926 uploadFileCallback.onReceiveValue( 927 uri == null ? null : new String[] { uri.toString() }); 928 } 929 }; 930 if (TRACE) Log.d(TAG, "openFileChooser"); 931 mWebChromeClient.openFileChooser(innerCallback, fileChooserParams.acceptTypes, 932 fileChooserParams.capture ? "*" : ""); 933 TraceEvent.end(); 934 } 935 936 @Override 937 public void onScaleChangedScaled(float oldScale, float newScale) { 938 TraceEvent.begin(); 939 if (TRACE) Log.d(TAG, " onScaleChangedScaled"); 940 mWebViewClient.onScaleChanged(mWebView, oldScale, newScale); 941 TraceEvent.end(); 942 } 943 944 @Override 945 public void onShowCustomView(View view, CustomViewCallback cb) { 946 TraceEvent.begin(); 947 if (mWebChromeClient != null) { 948 if (TRACE) Log.d(TAG, "onShowCustomView"); 949 mWebChromeClient.onShowCustomView(view, cb); 950 } 951 TraceEvent.end(); 952 } 953 954 @Override 955 public void onHideCustomView() { 956 TraceEvent.begin(); 957 if (mWebChromeClient != null) { 958 if (TRACE) Log.d(TAG, "onHideCustomView"); 959 mWebChromeClient.onHideCustomView(); 960 } 961 TraceEvent.end(); 962 } 963 964 @Override 965 protected View getVideoLoadingProgressView() { 966 TraceEvent.begin(); 967 View result; 968 if (mWebChromeClient != null) { 969 if (TRACE) Log.d(TAG, "getVideoLoadingProgressView"); 970 result = mWebChromeClient.getVideoLoadingProgressView(); 971 } else { 972 result = null; 973 } 974 TraceEvent.end(); 975 return result; 976 } 977 978 @Override 979 public Bitmap getDefaultVideoPoster() { 980 TraceEvent.begin(); 981 Bitmap result = null; 982 if (mWebChromeClient != null) { 983 if (TRACE) Log.d(TAG, "getDefaultVideoPoster"); 984 result = mWebChromeClient.getDefaultVideoPoster(); 985 } 986 if (result == null) { 987 // The ic_media_video_poster icon is transparent so we need to draw it on a gray 988 // background. 989 Bitmap poster = BitmapFactory.decodeResource( 990 mWebView.getContext().getResources(), 991 R.drawable.ic_media_video_poster); 992 result = Bitmap.createBitmap(poster.getWidth(), poster.getHeight(), poster.getConfig()); 993 result.eraseColor(Color.GRAY); 994 Canvas canvas = new Canvas(result); 995 canvas.drawBitmap(poster, 0f, 0f, null); 996 } 997 TraceEvent.end(); 998 return result; 999 } 1000 1001 // TODO: Move to upstream. 1002 private static class AwHttpAuthHandlerAdapter extends android.webkit.HttpAuthHandler { 1003 private AwHttpAuthHandler mAwHandler; 1004 1005 public AwHttpAuthHandlerAdapter(AwHttpAuthHandler awHandler) { 1006 mAwHandler = awHandler; 1007 } 1008 1009 @Override 1010 public void proceed(String username, String password) { 1011 if (username == null) { 1012 username = ""; 1013 } 1014 1015 if (password == null) { 1016 password = ""; 1017 } 1018 mAwHandler.proceed(username, password); 1019 } 1020 1021 @Override 1022 public void cancel() { 1023 mAwHandler.cancel(); 1024 } 1025 1026 @Override 1027 public boolean useHttpAuthUsernamePassword() { 1028 return mAwHandler.isFirstAttempt(); 1029 } 1030 } 1031 1032 // TODO: Move to the upstream once the PermissionRequest is part of SDK. 1033 public static class PermissionRequestAdapter extends PermissionRequest { 1034 // TODO: Move the below definitions to AwPermissionRequest. 1035 private static long BITMASK_RESOURCE_VIDEO_CAPTURE = 1 << 1; 1036 private static long BITMASK_RESOURCE_AUDIO_CAPTURE = 1 << 2; 1037 private static long BITMASK_RESOURCE_PROTECTED_MEDIA_ID = 1 << 3; 1038 1039 public static long toAwPermissionResources(String[] resources) { 1040 long result = 0; 1041 for (String resource : resources) { 1042 if (resource.equals(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) 1043 result |= BITMASK_RESOURCE_VIDEO_CAPTURE; 1044 else if (resource.equals(PermissionRequest.RESOURCE_AUDIO_CAPTURE)) 1045 result |= BITMASK_RESOURCE_AUDIO_CAPTURE; 1046 else if (resource.equals(PermissionRequest.RESOURCE_PROTECTED_MEDIA_ID)) 1047 result |= BITMASK_RESOURCE_PROTECTED_MEDIA_ID; 1048 } 1049 return result; 1050 } 1051 1052 private static String[] toPermissionResources(long resources) { 1053 ArrayList<String> result = new ArrayList<String>(); 1054 if ((resources & BITMASK_RESOURCE_VIDEO_CAPTURE) != 0) 1055 result.add(PermissionRequest.RESOURCE_VIDEO_CAPTURE); 1056 if ((resources & BITMASK_RESOURCE_AUDIO_CAPTURE) != 0) 1057 result.add(PermissionRequest.RESOURCE_AUDIO_CAPTURE); 1058 if ((resources & BITMASK_RESOURCE_PROTECTED_MEDIA_ID) != 0) 1059 result.add(PermissionRequest.RESOURCE_PROTECTED_MEDIA_ID); 1060 String[] resource_array = new String[result.size()]; 1061 return result.toArray(resource_array); 1062 } 1063 1064 private AwPermissionRequest mAwPermissionRequest; 1065 private String[] mResources; 1066 1067 public PermissionRequestAdapter(AwPermissionRequest awPermissionRequest) { 1068 assert awPermissionRequest != null; 1069 mAwPermissionRequest = awPermissionRequest; 1070 } 1071 1072 @Override 1073 public Uri getOrigin() { 1074 return mAwPermissionRequest.getOrigin(); 1075 } 1076 1077 @Override 1078 public String[] getResources() { 1079 synchronized (this) { 1080 if (mResources == null) { 1081 mResources = toPermissionResources(mAwPermissionRequest.getResources()); 1082 } 1083 return mResources; 1084 } 1085 } 1086 1087 @Override 1088 public void grant(String[] resources) { 1089 long requestedResource = mAwPermissionRequest.getResources(); 1090 if ((requestedResource & toAwPermissionResources(resources)) == requestedResource) 1091 mAwPermissionRequest.grant(); 1092 else 1093 mAwPermissionRequest.deny(); 1094 } 1095 1096 @Override 1097 public void deny() { 1098 mAwPermissionRequest.deny(); 1099 } 1100 1101 } 1102} 1103