1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package com.android.webview.chromium; 6 7 import android.content.Context; 8 import android.content.Intent; 9 import android.graphics.Bitmap; 10 import android.graphics.BitmapFactory; 11 import android.graphics.Canvas; 12 import android.graphics.Color; 13 import android.graphics.Picture; 14 import android.net.Uri; 15 import android.net.http.SslError; 16 import android.os.Build; 17 import android.os.Handler; 18 import android.os.Message; 19 import android.view.KeyEvent; 20 import android.view.View; 21 import android.view.WindowManager; 22 import android.webkit.ClientCertRequest; 23 import android.webkit.ConsoleMessage; 24 import android.webkit.DownloadListener; 25 import android.webkit.GeolocationPermissions; 26 import android.webkit.JsDialogHelper; 27 import android.webkit.JsPromptResult; 28 import android.webkit.JsResult; 29 import android.webkit.PermissionRequest; 30 import android.webkit.SslErrorHandler; 31 import android.webkit.ValueCallback; 32 import android.webkit.WebChromeClient; 33 import android.webkit.WebResourceResponse; 34 import android.webkit.WebView; 35 import android.webkit.WebViewClient; 36 37 import com.android.webview.chromium.WebViewDelegateFactory.WebViewDelegate; 38 39 import org.chromium.android_webview.AwConsoleMessage; 40 import org.chromium.android_webview.AwContentsClient; 41 import org.chromium.android_webview.AwContentsClientBridge; 42 import org.chromium.android_webview.AwGeolocationPermissions; 43 import org.chromium.android_webview.AwHistogramRecorder; 44 import org.chromium.android_webview.AwHttpAuthHandler; 45 import org.chromium.android_webview.AwRenderProcessGoneDetail; 46 import org.chromium.android_webview.JsPromptResultReceiver; 47 import org.chromium.android_webview.JsResultReceiver; 48 import org.chromium.android_webview.permission.AwPermissionRequest; 49 import org.chromium.android_webview.permission.Resource; 50 import org.chromium.base.Callback; 51 import org.chromium.base.ContextUtils; 52 import org.chromium.base.Log; 53 import org.chromium.base.TraceEvent; 54 import org.chromium.base.metrics.ScopedSysTraceEvent; 55 import org.chromium.base.task.PostTask; 56 import org.chromium.components.embedder_support.util.WebResourceResponseInfo; 57 import org.chromium.content_public.browser.UiThreadTaskTraits; 58 59 import java.lang.ref.WeakReference; 60 import java.security.Principal; 61 import java.security.PrivateKey; 62 import java.security.cert.X509Certificate; 63 import java.util.ArrayList; 64 import java.util.WeakHashMap; 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 */ 83 class WebViewContentsClientAdapter extends SharedWebViewContentsClientAdapter { 84 // The WebChromeClient instance that was passed to WebView.setContentViewClient(). 85 private WebChromeClient mWebChromeClient; 86 // The listener receiving find-in-page API results. 87 private WebView.FindListener mFindListener; 88 // The listener receiving notifications of screen updates. 89 private WebView.PictureListener mPictureListener; 90 // Whether the picture listener is invalidate only (i.e. receives a null Picture) 91 private boolean mPictureListenerInvalidateOnly; 92 93 private DownloadListener mDownloadListener; 94 95 private Handler mUiThreadHandler; 96 97 private static final int NEW_WEBVIEW_CREATED = 100; 98 99 private WeakHashMap<AwPermissionRequest, WeakReference<PermissionRequestAdapter>> 100 mOngoingPermissionRequests; 101 102 /** 103 * Adapter constructor. 104 * 105 * @param webView the {@link WebView} instance that this adapter is serving. 106 */ 107 @SuppressWarnings("HandlerLeak") WebViewContentsClientAdapter( WebView webView, Context context, WebViewDelegate webViewDelegate)108 WebViewContentsClientAdapter( 109 WebView webView, Context context, WebViewDelegate webViewDelegate) { 110 super(webView, webViewDelegate, context); 111 try (ScopedSysTraceEvent event = 112 ScopedSysTraceEvent.scoped("WebViewContentsClientAdapter.constructor")) { 113 // See //android_webview/docs/how-does-on-create-window-work.md for more details. 114 mUiThreadHandler = new Handler() { 115 @Override 116 public void handleMessage(Message msg) { 117 switch (msg.what) { 118 case NEW_WEBVIEW_CREATED: 119 WebView.WebViewTransport t = (WebView.WebViewTransport) msg.obj; 120 WebView newWebView = t.getWebView(); 121 if (newWebView == mWebView) { 122 throw new IllegalArgumentException( 123 "Parent WebView cannot host its own popup window. Please " 124 + "use WebSettings.setSupportMultipleWindows(false)"); 125 } 126 127 if (newWebView != null 128 && newWebView.copyBackForwardList().getSize() != 0) { 129 throw new IllegalArgumentException( 130 "New WebView for popup window must not have been " 131 + " previously navigated."); 132 } 133 134 WebViewChromium.completeWindowCreation(mWebView, newWebView); 135 break; 136 default: 137 throw new IllegalStateException(); 138 } 139 } 140 }; 141 } 142 } 143 setWebChromeClient(WebChromeClient client)144 void setWebChromeClient(WebChromeClient client) { 145 mWebChromeClient = client; 146 } 147 getWebChromeClient()148 WebChromeClient getWebChromeClient() { 149 return mWebChromeClient; 150 } 151 setDownloadListener(DownloadListener listener)152 void setDownloadListener(DownloadListener listener) { 153 mDownloadListener = listener; 154 } 155 setFindListener(WebView.FindListener listener)156 void setFindListener(WebView.FindListener listener) { 157 mFindListener = listener; 158 } 159 setPictureListener(WebView.PictureListener listener, boolean invalidateOnly)160 void setPictureListener(WebView.PictureListener listener, boolean invalidateOnly) { 161 mPictureListener = listener; 162 mPictureListenerInvalidateOnly = invalidateOnly; 163 } 164 165 //-------------------------------------------------------------------------------------------- 166 // Adapter for all the methods. 167 //-------------------------------------------------------------------------------------------- 168 169 /** 170 * @see AwContentsClient#getVisitedHistory. 171 */ 172 @Override getVisitedHistory(Callback<String[]> callback)173 public void getVisitedHistory(Callback<String[]> callback) { 174 try { 175 TraceEvent.begin("WebViewContentsClientAdapter.getVisitedHistory"); 176 if (mWebChromeClient != null) { 177 if (TRACE) Log.i(TAG, "getVisitedHistory"); 178 mWebChromeClient.getVisitedHistory( 179 callback == null ? null : value -> callback.onResult(value)); 180 } 181 } finally { 182 TraceEvent.end("WebViewContentsClientAdapter.getVisitedHistory"); 183 } 184 } 185 186 /** 187 * @see AwContentsClient#doUpdateVisiteHistory(String, boolean) 188 */ 189 @Override doUpdateVisitedHistory(String url, boolean isReload)190 public void doUpdateVisitedHistory(String url, boolean isReload) { 191 try { 192 TraceEvent.begin("WebViewContentsClientAdapter.doUpdateVisitedHistory"); 193 if (TRACE) Log.i(TAG, "doUpdateVisitedHistory=" + url + " reload=" + isReload); 194 mWebViewClient.doUpdateVisitedHistory(mWebView, url, isReload); 195 } finally { 196 TraceEvent.end("WebViewContentsClientAdapter.doUpdateVisitedHistory"); 197 } 198 } 199 200 /** 201 * @see AwContentsClient#onProgressChanged(int) 202 */ 203 @Override onProgressChanged(int progress)204 public void onProgressChanged(int progress) { 205 try { 206 TraceEvent.begin("WebViewContentsClientAdapter.onProgressChanged"); 207 if (mWebChromeClient != null) { 208 if (TRACE) Log.i(TAG, "onProgressChanged=" + progress); 209 mWebChromeClient.onProgressChanged(mWebView, progress); 210 } 211 } finally { 212 TraceEvent.end("WebViewContentsClientAdapter.onProgressChanged"); 213 } 214 } 215 216 /** 217 * @see AwContentsClient#shouldInterceptRequest(java.lang.String) 218 */ 219 @Override shouldInterceptRequest(AwWebResourceRequest request)220 public WebResourceResponseInfo shouldInterceptRequest(AwWebResourceRequest request) { 221 try { 222 TraceEvent.begin("WebViewContentsClientAdapter.shouldInterceptRequest"); 223 if (TRACE) Log.i(TAG, "shouldInterceptRequest=" + request.url); 224 WebResourceResponse response = mWebViewClient.shouldInterceptRequest( 225 mWebView, new WebResourceRequestAdapter(request)); 226 if (response == null) return null; 227 228 return new WebResourceResponseInfo(response.getMimeType(), response.getEncoding(), 229 response.getData(), response.getStatusCode(), response.getReasonPhrase(), 230 response.getResponseHeaders()); 231 } finally { 232 TraceEvent.end("WebViewContentsClientAdapter.shouldInterceptRequest"); 233 } 234 } 235 236 /** 237 * @see AwContentsClient#onUnhandledKeyEvent(android.view.KeyEvent) 238 */ 239 @Override onUnhandledKeyEvent(KeyEvent event)240 public void onUnhandledKeyEvent(KeyEvent event) { 241 try { 242 TraceEvent.begin("WebViewContentsClientAdapter.onUnhandledKeyEvent"); 243 if (TRACE) Log.i(TAG, "onUnhandledKeyEvent"); 244 mWebViewClient.onUnhandledKeyEvent(mWebView, event); 245 } finally { 246 TraceEvent.end("WebViewContentsClientAdapter.onUnhandledKeyEvent"); 247 } 248 } 249 250 /** 251 * @see AwContentsClient#onConsoleMessage(android.webkit.ConsoleMessage) 252 */ 253 @Override onConsoleMessage(AwConsoleMessage consoleMessage)254 public boolean onConsoleMessage(AwConsoleMessage consoleMessage) { 255 try { 256 TraceEvent.begin("WebViewContentsClientAdapter.onConsoleMessage"); 257 boolean result; 258 if (mWebChromeClient != null) { 259 if (TRACE) Log.i(TAG, "onConsoleMessage: " + consoleMessage.message()); 260 result = mWebChromeClient.onConsoleMessage(fromAwConsoleMessage(consoleMessage)); 261 } else { 262 result = false; 263 } 264 return result; 265 } finally { 266 TraceEvent.end("WebViewContentsClientAdapter.onConsoleMessage"); 267 } 268 } 269 270 /** 271 * @see AwContentsClient#onFindResultReceived(int,int,boolean) 272 */ 273 @Override onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting)274 public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, 275 boolean isDoneCounting) { 276 try { 277 TraceEvent.begin("WebViewContentsClientAdapter.onFindResultReceived"); 278 if (mFindListener == null) return; 279 if (TRACE) Log.i(TAG, "onFindResultReceived"); 280 mFindListener.onFindResultReceived(activeMatchOrdinal, numberOfMatches, isDoneCounting); 281 } finally { 282 TraceEvent.end("WebViewContentsClientAdapter.onFindResultReceived"); 283 } 284 } 285 286 /** 287 * @See AwContentsClient#onNewPicture(Picture) 288 */ 289 @Override onNewPicture(Picture picture)290 public void onNewPicture(Picture picture) { 291 try { 292 TraceEvent.begin("WebViewContentsClientAdapter.onNewPicture"); 293 if (mPictureListener == null) return; 294 if (TRACE) Log.i(TAG, "onNewPicture"); 295 mPictureListener.onNewPicture(mWebView, picture); 296 } finally { 297 TraceEvent.end("WebViewContentsClientAdapter.onNewPicture"); 298 } 299 } 300 301 @Override onLoadResource(String url)302 public void onLoadResource(String url) { 303 try { 304 TraceEvent.begin("WebViewContentsClientAdapter.onLoadResource"); 305 if (TRACE) Log.i(TAG, "onLoadResource=" + url); 306 mWebViewClient.onLoadResource(mWebView, url); 307 308 // Record UMA for onLoadResource. 309 AwHistogramRecorder.recordCallbackInvocation( 310 AwHistogramRecorder.WebViewCallbackType.ON_LOAD_RESOURCE); 311 } finally { 312 TraceEvent.end("WebViewContentsClientAdapter.onLoadResource"); 313 } 314 } 315 316 @Override onCreateWindow(boolean isDialog, boolean isUserGesture)317 public boolean onCreateWindow(boolean isDialog, boolean isUserGesture) { 318 try { 319 TraceEvent.begin("WebViewContentsClientAdapter.onCreateWindow"); 320 Message m = mUiThreadHandler.obtainMessage( 321 NEW_WEBVIEW_CREATED, mWebView.new WebViewTransport()); 322 boolean result; 323 if (mWebChromeClient != null) { 324 if (TRACE) Log.i(TAG, "onCreateWindow"); 325 result = mWebChromeClient.onCreateWindow(mWebView, isDialog, isUserGesture, m); 326 } else { 327 result = false; 328 } 329 return result; 330 } finally { 331 TraceEvent.end("WebViewContentsClientAdapter.onCreateWindow"); 332 } 333 } 334 335 /** 336 * @see AwContentsClient#onCloseWindow() 337 */ 338 @Override onCloseWindow()339 public void onCloseWindow() { 340 try { 341 TraceEvent.begin("WebViewContentsClientAdapter.onCloseWindow"); 342 if (mWebChromeClient != null) { 343 if (TRACE) Log.i(TAG, "onCloseWindow"); 344 mWebChromeClient.onCloseWindow(mWebView); 345 } 346 } finally { 347 TraceEvent.end("WebViewContentsClientAdapter.onCloseWindow"); 348 } 349 } 350 351 /** 352 * @see AwContentsClient#onRequestFocus() 353 */ 354 @Override onRequestFocus()355 public void onRequestFocus() { 356 try { 357 TraceEvent.begin("WebViewContentsClientAdapter.onRequestFocus"); 358 if (mWebChromeClient != null) { 359 if (TRACE) Log.i(TAG, "onRequestFocus"); 360 mWebChromeClient.onRequestFocus(mWebView); 361 } 362 } finally { 363 TraceEvent.end("WebViewContentsClientAdapter.onRequestFocus"); 364 } 365 } 366 367 /** 368 * @see AwContentsClient#onReceivedTouchIconUrl(String url, boolean precomposed) 369 */ 370 @Override onReceivedTouchIconUrl(String url, boolean precomposed)371 public void onReceivedTouchIconUrl(String url, boolean precomposed) { 372 try { 373 TraceEvent.begin("WebViewContentsClientAdapter.onReceivedTouchIconUrl"); 374 if (mWebChromeClient != null) { 375 if (TRACE) Log.i(TAG, "onReceivedTouchIconUrl=" + url); 376 mWebChromeClient.onReceivedTouchIconUrl(mWebView, url, precomposed); 377 } 378 } finally { 379 TraceEvent.end("WebViewContentsClientAdapter.onReceivedTouchIconUrl"); 380 } 381 } 382 383 /** 384 * @see AwContentsClient#onReceivedIcon(Bitmap bitmap) 385 */ 386 @Override onReceivedIcon(Bitmap bitmap)387 public void onReceivedIcon(Bitmap bitmap) { 388 try { 389 TraceEvent.begin("WebViewContentsClientAdapter.onReceivedIcon"); 390 if (mWebChromeClient != null) { 391 if (TRACE) Log.i(TAG, "onReceivedIcon"); 392 mWebChromeClient.onReceivedIcon(mWebView, bitmap); 393 } 394 } finally { 395 TraceEvent.end("WebViewContentsClientAdapter.onReceivedIcon"); 396 } 397 } 398 399 /** 400 * @see ContentViewClient#onPageStarted(String) 401 */ 402 @Override onPageStarted(String url)403 public void onPageStarted(String url) { 404 try { 405 TraceEvent.begin("WebViewContentsClientAdapter.onPageStarted"); 406 if (TRACE) Log.i(TAG, "onPageStarted=" + url); 407 mWebViewClient.onPageStarted(mWebView, url, mWebView.getFavicon()); 408 409 // Record UMA for onPageStarted. 410 AwHistogramRecorder.recordCallbackInvocation( 411 AwHistogramRecorder.WebViewCallbackType.ON_PAGE_STARTED); 412 413 } finally { 414 TraceEvent.end("WebViewContentsClientAdapter.onPageStarted"); 415 } 416 } 417 418 /** 419 * @see ContentViewClient#onPageFinished(String) 420 */ 421 @Override onPageFinished(String url)422 public void onPageFinished(String url) { 423 try { 424 TraceEvent.begin("WebViewContentsClientAdapter.onPageFinished"); 425 if (TRACE) Log.i(TAG, "onPageFinished=" + url); 426 mWebViewClient.onPageFinished(mWebView, url); 427 428 // Record UMA for onPageFinished. 429 AwHistogramRecorder.recordCallbackInvocation( 430 AwHistogramRecorder.WebViewCallbackType.ON_PAGE_FINISHED); 431 432 // See b/8208948 433 // This fakes an onNewPicture callback after onPageFinished to allow 434 // CTS tests to run in an un-flaky manner. This is required as the 435 // path for sending Picture updates in Chromium are decoupled from the 436 // page loading callbacks, i.e. the Chrome compositor may draw our 437 // content and send the Picture before onPageStarted or onPageFinished 438 // are invoked. The CTS harness discards any pictures it receives before 439 // onPageStarted is invoked, so in the case we get the Picture before that and 440 // no further updates after onPageStarted, we'll fail the test by timing 441 // out waiting for a Picture. 442 if (mPictureListener != null) { 443 PostTask.postDelayedTask(UiThreadTaskTraits.DEFAULT, () -> { 444 if (mPictureListener != null) { 445 if (TRACE) Log.i(TAG, "onPageFinished-fake"); 446 mPictureListener.onNewPicture( 447 mWebView, mPictureListenerInvalidateOnly ? null : new Picture()); 448 } 449 }, 100); 450 } 451 } finally { 452 TraceEvent.end("WebViewContentsClientAdapter.onPageFinished"); 453 } 454 } 455 456 /** 457 * @see ContentViewClient#onReceivedTitle(String) 458 */ 459 @Override onReceivedTitle(String title)460 public void onReceivedTitle(String title) { 461 try { 462 TraceEvent.begin("WebViewContentsClientAdapter.onReceivedTitle"); 463 if (mWebChromeClient != null) { 464 if (TRACE) Log.i(TAG, "onReceivedTitle=\"" + title + "\""); 465 mWebChromeClient.onReceivedTitle(mWebView, title); 466 } 467 } finally { 468 TraceEvent.end("WebViewContentsClientAdapter.onReceivedTitle"); 469 } 470 } 471 472 /** 473 * @see ContentViewClient#shouldOverrideKeyEvent(KeyEvent) 474 */ 475 @Override shouldOverrideKeyEvent(KeyEvent event)476 public boolean shouldOverrideKeyEvent(KeyEvent event) { 477 try { 478 TraceEvent.begin("WebViewContentsClientAdapter.shouldOverrideKeyEvent"); 479 if (TRACE) Log.i(TAG, "shouldOverrideKeyEvent"); 480 return mWebViewClient.shouldOverrideKeyEvent(mWebView, event); 481 } finally { 482 TraceEvent.end("WebViewContentsClientAdapter.shouldOverrideKeyEvent"); 483 } 484 } 485 486 /** 487 * Returns true if a method with a given name and parameters is declared in a subclass 488 * of a given baseclass. 489 */ isMethodDeclaredInSubClass(Class<T> baseClass, Class<? extends T> subClass, String name, Class<?>... parameterTypes)490 private static <T> boolean isMethodDeclaredInSubClass(Class<T> baseClass, 491 Class<? extends T> subClass, String name, Class<?>... parameterTypes) { 492 try { 493 return !subClass.getMethod(name, parameterTypes).getDeclaringClass().equals(baseClass); 494 } catch (SecurityException e) { 495 return false; 496 } catch (NoSuchMethodException e) { 497 return false; 498 } 499 } 500 501 @Override onGeolocationPermissionsShowPrompt( String origin, AwGeolocationPermissions.Callback callback)502 public void onGeolocationPermissionsShowPrompt( 503 String origin, AwGeolocationPermissions.Callback callback) { 504 try { 505 TraceEvent.begin("WebViewContentsClientAdapter.onGeolocationPermissionsShowPrompt"); 506 if (mWebChromeClient == null) { 507 callback.invoke(origin, false, false); 508 return; 509 } 510 if (!isMethodDeclaredInSubClass(WebChromeClient.class, 511 mWebChromeClient.getClass(), 512 "onGeolocationPermissionsShowPrompt", 513 String.class, 514 GeolocationPermissions.Callback.class)) { 515 // This is only required for pre-M versions of android. 516 callback.invoke(origin, false, false); 517 return; 518 } 519 if (TRACE) Log.i(TAG, "onGeolocationPermissionsShowPrompt"); 520 mWebChromeClient.onGeolocationPermissionsShowPrompt(origin, 521 callback == null ? null : (callbackOrigin, allow, retain) 522 -> callback.invoke(callbackOrigin, allow, retain)); 523 } finally { 524 TraceEvent.end("WebViewContentsClientAdapter.onGeolocationPermissionsShowPrompt"); 525 } 526 } 527 528 @Override onGeolocationPermissionsHidePrompt()529 public void onGeolocationPermissionsHidePrompt() { 530 try { 531 TraceEvent.begin("WebViewContentsClientAdapter.onGeolocationPermissionsHidePrompt"); 532 if (mWebChromeClient != null) { 533 if (TRACE) Log.i(TAG, "onGeolocationPermissionsHidePrompt"); 534 mWebChromeClient.onGeolocationPermissionsHidePrompt(); 535 } 536 } finally { 537 TraceEvent.end("WebViewContentsClientAdapter.onGeolocationPermissionsHidePrompt"); 538 } 539 } 540 541 @Override onPermissionRequest(AwPermissionRequest permissionRequest)542 public void onPermissionRequest(AwPermissionRequest permissionRequest) { 543 try { 544 TraceEvent.begin("WebViewContentsClientAdapter.onPermissionRequest"); 545 if (mWebChromeClient != null) { 546 if (TRACE) Log.i(TAG, "onPermissionRequest"); 547 if (mOngoingPermissionRequests == null) { 548 mOngoingPermissionRequests = new WeakHashMap<AwPermissionRequest, 549 WeakReference<PermissionRequestAdapter>>(); 550 } 551 PermissionRequestAdapter adapter = new PermissionRequestAdapter(permissionRequest); 552 mOngoingPermissionRequests.put( 553 permissionRequest, new WeakReference<PermissionRequestAdapter>(adapter)); 554 mWebChromeClient.onPermissionRequest(adapter); 555 } else { 556 // By default, we deny the permission. 557 permissionRequest.deny(); 558 } 559 } finally { 560 TraceEvent.end("WebViewContentsClientAdapter.onPermissionRequest"); 561 } 562 } 563 564 @Override onPermissionRequestCanceled(AwPermissionRequest permissionRequest)565 public void onPermissionRequestCanceled(AwPermissionRequest permissionRequest) { 566 try { 567 TraceEvent.begin("WebViewContentsClientAdapter.onPermissionRequestCanceled"); 568 if (mWebChromeClient != null && mOngoingPermissionRequests != null) { 569 if (TRACE) Log.i(TAG, "onPermissionRequestCanceled"); 570 WeakReference<PermissionRequestAdapter> weakRef = 571 mOngoingPermissionRequests.get(permissionRequest); 572 // We don't hold strong reference to PermissionRequestAdpater and don't expect the 573 // user only holds weak reference to it either, if so, user has no way to call 574 // grant()/deny(), and no need to be notified the cancellation of request. 575 if (weakRef != null) { 576 PermissionRequestAdapter adapter = weakRef.get(); 577 if (adapter != null) mWebChromeClient.onPermissionRequestCanceled(adapter); 578 } 579 } 580 } finally { 581 TraceEvent.end("WebViewContentsClientAdapter.onPermissionRequestCanceled"); 582 } 583 } 584 585 private static class JsPromptResultReceiverAdapter implements JsResult.ResultReceiver { 586 private JsPromptResultReceiver mChromePromptResultReceiver; 587 private JsResultReceiver mChromeResultReceiver; 588 // We hold onto the JsPromptResult here, just to avoid the need to downcast 589 // in onJsResultComplete. 590 private final JsPromptResult mPromptResult = new JsPromptResult(this); 591 JsPromptResultReceiverAdapter(JsPromptResultReceiver receiver)592 public JsPromptResultReceiverAdapter(JsPromptResultReceiver receiver) { 593 mChromePromptResultReceiver = receiver; 594 } 595 JsPromptResultReceiverAdapter(JsResultReceiver receiver)596 public JsPromptResultReceiverAdapter(JsResultReceiver receiver) { 597 mChromeResultReceiver = receiver; 598 } 599 getPromptResult()600 public JsPromptResult getPromptResult() { 601 return mPromptResult; 602 } 603 604 @Override onJsResultComplete(JsResult result)605 public void onJsResultComplete(JsResult result) { 606 if (mChromePromptResultReceiver != null) { 607 if (mPromptResult.getResult()) { 608 mChromePromptResultReceiver.confirm(mPromptResult.getStringResult()); 609 } else { 610 mChromePromptResultReceiver.cancel(); 611 } 612 } else { 613 if (mPromptResult.getResult()) { 614 mChromeResultReceiver.confirm(); 615 } else { 616 mChromeResultReceiver.cancel(); 617 } 618 } 619 } 620 } 621 622 @Override handleJsAlert(String url, String message, JsResultReceiver receiver)623 public void handleJsAlert(String url, String message, JsResultReceiver receiver) { 624 try { 625 TraceEvent.begin("WebViewContentsClientAdapter.handleJsAlert"); 626 if (mWebChromeClient != null) { 627 final JsPromptResult res = 628 new JsPromptResultReceiverAdapter(receiver).getPromptResult(); 629 if (TRACE) Log.i(TAG, "onJsAlert"); 630 if (!mWebChromeClient.onJsAlert(mWebView, url, message, res)) { 631 if (!showDefaultJsDialog(res, JsDialogHelper.ALERT, null, message, url)) { 632 receiver.cancel(); 633 } 634 } 635 } else { 636 receiver.cancel(); 637 } 638 } finally { 639 TraceEvent.end("WebViewContentsClientAdapter.handleJsAlert"); 640 } 641 } 642 643 @Override handleJsBeforeUnload(String url, String message, JsResultReceiver receiver)644 public void handleJsBeforeUnload(String url, String message, JsResultReceiver receiver) { 645 try { 646 TraceEvent.begin("WebViewContentsClientAdapter.handleJsBeforeUnload"); 647 if (mWebChromeClient != null) { 648 final JsPromptResult res = 649 new JsPromptResultReceiverAdapter(receiver).getPromptResult(); 650 if (TRACE) Log.i(TAG, "onJsBeforeUnload"); 651 if (!mWebChromeClient.onJsBeforeUnload(mWebView, url, message, res)) { 652 if (!showDefaultJsDialog(res, JsDialogHelper.UNLOAD, null, message, url)) { 653 receiver.cancel(); 654 } 655 } 656 } else { 657 receiver.cancel(); 658 } 659 } finally { 660 TraceEvent.end("WebViewContentsClientAdapter.handleJsBeforeUnload"); 661 } 662 } 663 664 @Override handleJsConfirm(String url, String message, JsResultReceiver receiver)665 public void handleJsConfirm(String url, String message, JsResultReceiver receiver) { 666 try { 667 TraceEvent.begin("WebViewContentsClientAdapter.handleJsConfirm"); 668 if (mWebChromeClient != null) { 669 final JsPromptResult res = 670 new JsPromptResultReceiverAdapter(receiver).getPromptResult(); 671 if (TRACE) Log.i(TAG, "onJsConfirm"); 672 if (!mWebChromeClient.onJsConfirm(mWebView, url, message, res)) { 673 if (!showDefaultJsDialog(res, JsDialogHelper.CONFIRM, null, message, url)) { 674 receiver.cancel(); 675 } 676 } 677 } else { 678 receiver.cancel(); 679 } 680 } finally { 681 TraceEvent.end("WebViewContentsClientAdapter.handleJsConfirm"); 682 } 683 } 684 685 @Override handleJsPrompt(String url, String message, String defaultValue, JsPromptResultReceiver receiver)686 public void handleJsPrompt(String url, String message, String defaultValue, 687 JsPromptResultReceiver receiver) { 688 try { 689 TraceEvent.begin("WebViewContentsClientAdapter.handleJsPrompt"); 690 if (mWebChromeClient != null) { 691 final JsPromptResult res = 692 new JsPromptResultReceiverAdapter(receiver).getPromptResult(); 693 if (TRACE) Log.i(TAG, "onJsPrompt"); 694 if (!mWebChromeClient.onJsPrompt(mWebView, url, message, defaultValue, res)) { 695 if (!showDefaultJsDialog( 696 res, JsDialogHelper.PROMPT, defaultValue, message, url)) { 697 receiver.cancel(); 698 } 699 } 700 } else { 701 receiver.cancel(); 702 } 703 } finally { 704 TraceEvent.end("WebViewContentsClientAdapter.handleJsPrompt"); 705 } 706 } 707 708 /** 709 * Try to show the default JS dialog and return whether the dialog was shown. 710 */ showDefaultJsDialog(JsPromptResult res, int jsDialogType, String defaultValue, String message, String url)711 private boolean showDefaultJsDialog(JsPromptResult res, int jsDialogType, String defaultValue, 712 String message, String url) { 713 // Note we must unwrap the Context here due to JsDialogHelper only using instanceof to 714 // check if a Context is an Activity. 715 Context activityContext = ContextUtils.activityFromContext(mContext); 716 if (activityContext == null) { 717 Log.w(TAG, "Unable to create JsDialog without an Activity"); 718 return false; 719 } 720 try { 721 new JsDialogHelper(res, jsDialogType, defaultValue, message, url) 722 .showDialog(activityContext); 723 } catch (WindowManager.BadTokenException e) { 724 Log.w(TAG, 725 "Unable to create JsDialog. Has this WebView outlived the Activity it was created with?"); 726 return false; 727 } 728 return true; 729 } 730 731 @Override onReceivedHttpAuthRequest(AwHttpAuthHandler handler, String host, String realm)732 public void onReceivedHttpAuthRequest(AwHttpAuthHandler handler, String host, String realm) { 733 try { 734 TraceEvent.begin("WebViewContentsClientAdapter.onReceivedHttpAuthRequest"); 735 if (TRACE) Log.i(TAG, "onReceivedHttpAuthRequest=" + host); 736 mWebViewClient.onReceivedHttpAuthRequest( 737 mWebView, new AwHttpAuthHandlerAdapter(handler), host, realm); 738 } finally { 739 TraceEvent.end("WebViewContentsClientAdapter.onReceivedHttpAuthRequest"); 740 } 741 } 742 743 @Override 744 @SuppressWarnings("HandlerLeak") onReceivedSslError(final Callback<Boolean> callback, SslError error)745 public void onReceivedSslError(final Callback<Boolean> callback, SslError error) { 746 try { 747 TraceEvent.begin("WebViewContentsClientAdapter.onReceivedSslError"); 748 SslErrorHandler handler = new SslErrorHandler() { 749 @Override 750 public void proceed() { 751 callback.onResult(true); 752 } 753 @Override 754 public void cancel() { 755 callback.onResult(false); 756 } 757 }; 758 if (TRACE) Log.i(TAG, "onReceivedSslError"); 759 mWebViewClient.onReceivedSslError(mWebView, handler, error); 760 } finally { 761 TraceEvent.end("WebViewContentsClientAdapter.onReceivedSslError"); 762 } 763 } 764 765 private static class ClientCertRequestImpl extends ClientCertRequest { 766 private final AwContentsClientBridge.ClientCertificateRequestCallback mCallback; 767 private final String[] mKeyTypes; 768 private final Principal[] mPrincipals; 769 private final String mHost; 770 private final int mPort; 771 ClientCertRequestImpl( AwContentsClientBridge.ClientCertificateRequestCallback callback, String[] keyTypes, Principal[] principals, String host, int port)772 public ClientCertRequestImpl( 773 AwContentsClientBridge.ClientCertificateRequestCallback callback, String[] keyTypes, 774 Principal[] principals, String host, int port) { 775 mCallback = callback; 776 mKeyTypes = keyTypes; 777 mPrincipals = principals; 778 mHost = host; 779 mPort = port; 780 } 781 782 @Override getKeyTypes()783 public String[] getKeyTypes() { 784 // This is already a copy of native argument, so return directly. 785 return mKeyTypes; 786 } 787 788 @Override getPrincipals()789 public Principal[] getPrincipals() { 790 // This is already a copy of native argument, so return directly. 791 return mPrincipals; 792 } 793 794 @Override getHost()795 public String getHost() { 796 return mHost; 797 } 798 799 @Override getPort()800 public int getPort() { 801 return mPort; 802 } 803 804 @Override proceed(final PrivateKey privateKey, final X509Certificate[] chain)805 public void proceed(final PrivateKey privateKey, final X509Certificate[] chain) { 806 mCallback.proceed(privateKey, chain); 807 } 808 809 @Override ignore()810 public void ignore() { 811 mCallback.ignore(); 812 } 813 814 @Override cancel()815 public void cancel() { 816 mCallback.cancel(); 817 } 818 } 819 820 @Override onReceivedClientCertRequest( AwContentsClientBridge.ClientCertificateRequestCallback callback, String[] keyTypes, Principal[] principals, String host, int port)821 public void onReceivedClientCertRequest( 822 AwContentsClientBridge.ClientCertificateRequestCallback callback, String[] keyTypes, 823 Principal[] principals, String host, int port) { 824 if (TRACE) Log.i(TAG, "onReceivedClientCertRequest"); 825 try { 826 TraceEvent.begin("WebViewContentsClientAdapter.onReceivedClientCertRequest"); 827 final ClientCertRequestImpl request = 828 new ClientCertRequestImpl(callback, keyTypes, principals, host, port); 829 mWebViewClient.onReceivedClientCertRequest(mWebView, request); 830 } finally { 831 TraceEvent.end("WebViewContentsClientAdapter.onReceivedClientCertRequest"); 832 } 833 } 834 835 @Override onReceivedLoginRequest(String realm, String account, String args)836 public void onReceivedLoginRequest(String realm, String account, String args) { 837 try { 838 TraceEvent.begin("WebViewContentsClientAdapter.onReceivedLoginRequest"); 839 if (TRACE) Log.i(TAG, "onReceivedLoginRequest=" + realm); 840 mWebViewClient.onReceivedLoginRequest(mWebView, realm, account, args); 841 } finally { 842 TraceEvent.end("WebViewContentsClientAdapter.onReceivedLoginRequest"); 843 } 844 } 845 846 @Override onFormResubmission(Message dontResend, Message resend)847 public void onFormResubmission(Message dontResend, Message resend) { 848 try { 849 TraceEvent.begin("WebViewContentsClientAdapter.onFormResubmission"); 850 if (TRACE) Log.i(TAG, "onFormResubmission"); 851 mWebViewClient.onFormResubmission(mWebView, dontResend, resend); 852 } finally { 853 TraceEvent.end("WebViewContentsClientAdapter.onFormResubmission"); 854 } 855 } 856 857 @Override onDownloadStart( String url, String userAgent, String contentDisposition, String mimeType, long contentLength)858 public void onDownloadStart( 859 String url, 860 String userAgent, 861 String contentDisposition, 862 String mimeType, 863 long contentLength) { 864 try { 865 TraceEvent.begin("WebViewContentsClientAdapter.onDownloadStart"); 866 if (mDownloadListener != null) { 867 if (TRACE) Log.i(TAG, "onDownloadStart"); 868 mDownloadListener.onDownloadStart( 869 url, userAgent, contentDisposition, mimeType, contentLength); 870 } 871 } finally { 872 TraceEvent.end("WebViewContentsClientAdapter.onDownloadStart"); 873 } 874 } 875 876 @Override showFileChooser(final Callback<String[]> uploadFileCallback, final AwContentsClient.FileChooserParamsImpl fileChooserParams)877 public void showFileChooser(final Callback<String[]> uploadFileCallback, 878 final AwContentsClient.FileChooserParamsImpl fileChooserParams) { 879 try { 880 TraceEvent.begin("WebViewContentsClientAdapter.showFileChooser"); 881 if (mWebChromeClient == null) { 882 uploadFileCallback.onResult(null); 883 return; 884 } 885 if (TRACE) Log.i(TAG, "showFileChooser"); 886 ValueCallback<Uri[]> callbackAdapter = new ValueCallback<Uri[]>() { 887 private boolean mCompleted; 888 @Override 889 public void onReceiveValue(Uri[] uriList) { 890 if (mCompleted) { 891 throw new IllegalStateException( 892 "showFileChooser result was already called"); 893 } 894 mCompleted = true; 895 String s[] = null; 896 if (uriList != null) { 897 s = new String[uriList.length]; 898 for (int i = 0; i < uriList.length; i++) { 899 s[i] = uriList[i].toString(); 900 } 901 } 902 uploadFileCallback.onResult(s); 903 } 904 }; 905 906 // Invoke the new callback introduced in Lollipop. If the app handles 907 // it, we're done here. 908 if (mWebChromeClient.onShowFileChooser( 909 mWebView, callbackAdapter, fromAwFileChooserParams(fileChooserParams))) { 910 return; 911 } 912 913 // If the app did not handle it and we are running on Lollipop or newer, then 914 // abort. 915 if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) { 916 uploadFileCallback.onResult(null); 917 return; 918 } 919 920 // Otherwise, for older apps, attempt to invoke the legacy (hidden) API for 921 // backwards compatibility. 922 ValueCallback<Uri> innerCallback = new ValueCallback<Uri>() { 923 private boolean mCompleted; 924 @Override 925 public void onReceiveValue(Uri uri) { 926 if (mCompleted) { 927 throw new IllegalStateException( 928 "showFileChooser result was already called"); 929 } 930 mCompleted = true; 931 uploadFileCallback.onResult(uri == null ? null : new String[] {uri.toString()}); 932 } 933 }; 934 if (TRACE) Log.i(TAG, "openFileChooser"); 935 mWebChromeClient.openFileChooser( 936 innerCallback, 937 fileChooserParams.getAcceptTypesString(), 938 fileChooserParams.isCaptureEnabled() ? "*" : ""); 939 } finally { 940 TraceEvent.end("WebViewContentsClientAdapter.showFileChooser"); 941 } 942 } 943 944 @Override onScaleChangedScaled(float oldScale, float newScale)945 public void onScaleChangedScaled(float oldScale, float newScale) { 946 try { 947 TraceEvent.begin("WebViewContentsClientAdapter.onScaleChangedScaled"); 948 if (TRACE) Log.i(TAG, " onScaleChangedScaled"); 949 mWebViewClient.onScaleChanged(mWebView, oldScale, newScale); 950 } finally { 951 TraceEvent.end("WebViewContentsClientAdapter.onScaleChangedScaled"); 952 } 953 } 954 955 @Override onShowCustomView(View view, final CustomViewCallback cb)956 public void onShowCustomView(View view, final CustomViewCallback cb) { 957 try { 958 TraceEvent.begin("WebViewContentsClientAdapter.onShowCustomView"); 959 if (mWebChromeClient != null) { 960 if (TRACE) Log.i(TAG, "onShowCustomView"); 961 mWebChromeClient.onShowCustomView( 962 view, cb == null ? null : () -> cb.onCustomViewHidden()); 963 } 964 } finally { 965 TraceEvent.end("WebViewContentsClientAdapter.onShowCustomView"); 966 } 967 } 968 969 @Override onHideCustomView()970 public void onHideCustomView() { 971 try { 972 TraceEvent.begin("WebViewContentsClientAdapter.onHideCustomView"); 973 if (mWebChromeClient != null) { 974 if (TRACE) Log.i(TAG, "onHideCustomView"); 975 mWebChromeClient.onHideCustomView(); 976 } 977 } finally { 978 TraceEvent.end("WebViewContentsClientAdapter.onHideCustomView"); 979 } 980 } 981 982 @Override getVideoLoadingProgressView()983 protected View getVideoLoadingProgressView() { 984 try { 985 TraceEvent.begin("WebViewContentsClientAdapter.getVideoLoadingProgressView"); 986 View result; 987 if (mWebChromeClient != null) { 988 if (TRACE) Log.i(TAG, "getVideoLoadingProgressView"); 989 result = mWebChromeClient.getVideoLoadingProgressView(); 990 } else { 991 result = null; 992 } 993 return result; 994 } finally { 995 TraceEvent.end("WebViewContentsClientAdapter.getVideoLoadingProgressView"); 996 } 997 } 998 999 @Override getDefaultVideoPoster()1000 public Bitmap getDefaultVideoPoster() { 1001 try { 1002 TraceEvent.begin("WebViewContentsClientAdapter.getDefaultVideoPoster"); 1003 Bitmap result = null; 1004 if (mWebChromeClient != null) { 1005 if (TRACE) Log.i(TAG, "getDefaultVideoPoster"); 1006 result = mWebChromeClient.getDefaultVideoPoster(); 1007 } 1008 if (result == null) { 1009 // The ic_play_circle_outline_black_48dp icon is transparent so we need to draw it 1010 // on a gray background. 1011 Bitmap poster = BitmapFactory.decodeResource( 1012 mContext.getResources(), 1013 org.chromium.android_webview.R.drawable.ic_play_circle_outline_black_48dp); 1014 result = Bitmap.createBitmap( 1015 poster.getWidth(), poster.getHeight(), poster.getConfig()); 1016 result.eraseColor(Color.GRAY); 1017 Canvas canvas = new Canvas(result); 1018 canvas.drawBitmap(poster, 0f, 0f, null); 1019 } 1020 return result; 1021 } finally { 1022 TraceEvent.end("WebViewContentsClientAdapter.getDefaultVideoPoster"); 1023 } 1024 } 1025 1026 @Override onRenderProcessGone(final AwRenderProcessGoneDetail detail)1027 public boolean onRenderProcessGone(final AwRenderProcessGoneDetail detail) { 1028 // WebViewClient.onRenderProcessGone was added in O. 1029 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return false; 1030 1031 try { 1032 TraceEvent.begin("WebViewContentsClientAdapter.onRenderProcessGone"); 1033 return GlueApiHelperForO.onRenderProcessGone(mWebViewClient, mWebView, detail); 1034 } finally { 1035 TraceEvent.end("WebViewContentsClientAdapter.onRenderProcessGone"); 1036 } 1037 } 1038 1039 private static class AwHttpAuthHandlerAdapter extends android.webkit.HttpAuthHandler { 1040 private AwHttpAuthHandler mAwHandler; 1041 AwHttpAuthHandlerAdapter(AwHttpAuthHandler awHandler)1042 public AwHttpAuthHandlerAdapter(AwHttpAuthHandler awHandler) { 1043 mAwHandler = awHandler; 1044 } 1045 1046 @Override proceed(String username, String password)1047 public void proceed(String username, String password) { 1048 if (username == null) { 1049 username = ""; 1050 } 1051 1052 if (password == null) { 1053 password = ""; 1054 } 1055 mAwHandler.proceed(username, password); 1056 } 1057 1058 @Override cancel()1059 public void cancel() { 1060 mAwHandler.cancel(); 1061 } 1062 1063 @Override useHttpAuthUsernamePassword()1064 public boolean useHttpAuthUsernamePassword() { 1065 return mAwHandler.isFirstAttempt(); 1066 } 1067 } 1068 1069 /** 1070 * Type adaptation class for PermissionRequest. 1071 */ 1072 public static class PermissionRequestAdapter extends PermissionRequest { 1073 toAwPermissionResources(String[] resources)1074 private static long toAwPermissionResources(String[] resources) { 1075 long result = 0; 1076 for (String resource : resources) { 1077 if (resource.equals(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) { 1078 result |= Resource.VIDEO_CAPTURE; 1079 } else if (resource.equals(PermissionRequest.RESOURCE_AUDIO_CAPTURE)) { 1080 result |= Resource.AUDIO_CAPTURE; 1081 } else if (resource.equals(PermissionRequest.RESOURCE_PROTECTED_MEDIA_ID)) { 1082 result |= Resource.PROTECTED_MEDIA_ID; 1083 } else if (resource.equals(AwPermissionRequest.RESOURCE_MIDI_SYSEX)) { 1084 result |= Resource.MIDI_SYSEX; 1085 } 1086 } 1087 return result; 1088 } 1089 toPermissionResources(long resources)1090 private static String[] toPermissionResources(long resources) { 1091 ArrayList<String> result = new ArrayList<String>(); 1092 if ((resources & Resource.VIDEO_CAPTURE) != 0) { 1093 result.add(PermissionRequest.RESOURCE_VIDEO_CAPTURE); 1094 } 1095 if ((resources & Resource.AUDIO_CAPTURE) != 0) { 1096 result.add(PermissionRequest.RESOURCE_AUDIO_CAPTURE); 1097 } 1098 if ((resources & Resource.PROTECTED_MEDIA_ID) != 0) { 1099 result.add(PermissionRequest.RESOURCE_PROTECTED_MEDIA_ID); 1100 } 1101 if ((resources & Resource.MIDI_SYSEX) != 0) { 1102 result.add(AwPermissionRequest.RESOURCE_MIDI_SYSEX); 1103 } 1104 String[] resource_array = new String[result.size()]; 1105 return result.toArray(resource_array); 1106 } 1107 1108 private AwPermissionRequest mAwPermissionRequest; 1109 private final String[] mResources; 1110 PermissionRequestAdapter(AwPermissionRequest awPermissionRequest)1111 public PermissionRequestAdapter(AwPermissionRequest awPermissionRequest) { 1112 assert awPermissionRequest != null; 1113 mAwPermissionRequest = awPermissionRequest; 1114 mResources = toPermissionResources(mAwPermissionRequest.getResources()); 1115 } 1116 1117 @Override getOrigin()1118 public Uri getOrigin() { 1119 return mAwPermissionRequest.getOrigin(); 1120 } 1121 1122 @Override getResources()1123 public String[] getResources() { 1124 return mResources.clone(); 1125 } 1126 1127 @Override grant(String[] resources)1128 public void grant(String[] resources) { 1129 long requestedResource = mAwPermissionRequest.getResources(); 1130 if ((requestedResource & toAwPermissionResources(resources)) == requestedResource) { 1131 mAwPermissionRequest.grant(); 1132 } else { 1133 mAwPermissionRequest.deny(); 1134 } 1135 } 1136 1137 @Override deny()1138 public void deny() { 1139 mAwPermissionRequest.deny(); 1140 } 1141 } 1142 fromAwFileChooserParams( final AwContentsClient.FileChooserParamsImpl value)1143 public static WebChromeClient.FileChooserParams fromAwFileChooserParams( 1144 final AwContentsClient.FileChooserParamsImpl value) { 1145 if (value == null) { 1146 return null; 1147 } 1148 return new WebChromeClient.FileChooserParams() { 1149 @Override 1150 public int getMode() { 1151 return value.getMode(); 1152 } 1153 1154 @Override 1155 public String[] getAcceptTypes() { 1156 return value.getAcceptTypes(); 1157 } 1158 1159 @Override 1160 public boolean isCaptureEnabled() { 1161 return value.isCaptureEnabled(); 1162 } 1163 1164 @Override 1165 public CharSequence getTitle() { 1166 return value.getTitle(); 1167 } 1168 1169 @Override 1170 public String getFilenameHint() { 1171 return value.getFilenameHint(); 1172 } 1173 1174 @Override 1175 public Intent createIntent() { 1176 return value.createIntent(); 1177 } 1178 }; 1179 } 1180 1181 private static ConsoleMessage fromAwConsoleMessage(AwConsoleMessage value) { 1182 if (value == null) { 1183 return null; 1184 } 1185 return new ConsoleMessage(value.message(), value.sourceId(), value.lineNumber(), 1186 fromAwMessageLevel(value.messageLevel())); 1187 } 1188 1189 private static ConsoleMessage.MessageLevel fromAwMessageLevel( 1190 @AwConsoleMessage.MessageLevel int value) { 1191 switch (value) { 1192 case AwConsoleMessage.MESSAGE_LEVEL_TIP: 1193 return ConsoleMessage.MessageLevel.TIP; 1194 case AwConsoleMessage.MESSAGE_LEVEL_LOG: 1195 return ConsoleMessage.MessageLevel.LOG; 1196 case AwConsoleMessage.MESSAGE_LEVEL_WARNING: 1197 return ConsoleMessage.MessageLevel.WARNING; 1198 case AwConsoleMessage.MESSAGE_LEVEL_ERROR: 1199 return ConsoleMessage.MessageLevel.ERROR; 1200 case AwConsoleMessage.MESSAGE_LEVEL_DEBUG: 1201 return ConsoleMessage.MessageLevel.DEBUG; 1202 default: 1203 throw new IllegalArgumentException("Unsupported value: " + value); 1204 } 1205 } 1206 } 1207