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