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