CallbackProxy.java revision 51ef573a8336c637ac508c49b84b512d8f11fa3a
1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.webkit;
18
19import android.app.AlertDialog;
20import android.content.ActivityNotFoundException;
21import android.content.Context;
22import android.content.DialogInterface;
23import android.content.Intent;
24import android.graphics.Bitmap;
25import android.net.Uri;
26import android.net.http.SslCertificate;
27import android.net.http.SslError;
28import android.os.Bundle;
29import android.os.Handler;
30import android.os.Message;
31import android.os.SystemClock;
32import android.provider.Browser;
33import android.util.Log;
34import android.view.KeyEvent;
35import android.view.LayoutInflater;
36import android.view.View;
37import android.widget.EditText;
38import android.widget.TextView;
39import com.android.internal.R;
40
41import java.net.MalformedURLException;
42import java.net.URL;
43import java.util.HashMap;
44
45/**
46 * This class is a proxy class for handling WebCore -> UI thread messaging. All
47 * the callback functions are called from the WebCore thread and messages are
48 * posted to the UI thread for the actual client callback.
49 */
50/*
51 * This class is created in the UI thread so its handler and any private classes
52 * that extend Handler will operate in the UI thread.
53 */
54class CallbackProxy extends Handler {
55    // Logging tag
56    private static final String LOGTAG = "CallbackProxy";
57    // Instance of WebViewClient that is the client callback.
58    private volatile WebViewClient mWebViewClient;
59    // Instance of WebChromeClient for handling all chrome functions.
60    private volatile WebChromeClient mWebChromeClient;
61    // Instance of WebView for handling UI requests.
62    private final WebView mWebView;
63    // Client registered callback listener for download events
64    private volatile DownloadListener mDownloadListener;
65    // Keep track of multiple progress updates.
66    private boolean mProgressUpdatePending;
67    // Keep track of the last progress amount.
68    // Start with 100 to indicate it is not in load for the empty page.
69    private volatile int mLatestProgress = 100;
70    // Back/Forward list
71    private final WebBackForwardList mBackForwardList;
72    // Used to call startActivity during url override.
73    private final Context mContext;
74
75    // Message Ids
76    private static final int PAGE_STARTED                        = 100;
77    private static final int RECEIVED_ICON                       = 101;
78    private static final int RECEIVED_TITLE                      = 102;
79    private static final int OVERRIDE_URL                        = 103;
80    private static final int AUTH_REQUEST                        = 104;
81    private static final int SSL_ERROR                           = 105;
82    private static final int PROGRESS                            = 106;
83    private static final int UPDATE_VISITED                      = 107;
84    private static final int LOAD_RESOURCE                       = 108;
85    private static final int CREATE_WINDOW                       = 109;
86    private static final int CLOSE_WINDOW                        = 110;
87    private static final int SAVE_PASSWORD                       = 111;
88    private static final int JS_ALERT                            = 112;
89    private static final int JS_CONFIRM                          = 113;
90    private static final int JS_PROMPT                           = 114;
91    private static final int JS_UNLOAD                           = 115;
92    private static final int ASYNC_KEYEVENTS                     = 116;
93    private static final int TOO_MANY_REDIRECTS                  = 117;
94    private static final int DOWNLOAD_FILE                       = 118;
95    private static final int REPORT_ERROR                        = 119;
96    private static final int RESEND_POST_DATA                    = 120;
97    private static final int PAGE_FINISHED                       = 121;
98    private static final int REQUEST_FOCUS                       = 122;
99    private static final int SCALE_CHANGED                       = 123;
100    private static final int RECEIVED_CERTIFICATE                = 124;
101    private static final int SWITCH_OUT_HISTORY                  = 125;
102    private static final int EXCEEDED_DATABASE_QUOTA             = 126;
103    private static final int REACHED_APPCACHE_MAXSIZE            = 127;
104    private static final int JS_TIMEOUT                          = 128;
105    private static final int ADD_MESSAGE_TO_CONSOLE              = 129;
106    private static final int GEOLOCATION_PERMISSIONS_SHOW_PROMPT = 130;
107    private static final int GEOLOCATION_PERMISSIONS_HIDE_PROMPT = 131;
108    private static final int RECEIVED_TOUCH_ICON_URL             = 132;
109    private static final int GET_VISITED_HISTORY                 = 133;
110    private static final int OPEN_FILE_CHOOSER                   = 134;
111    private static final int SHOW_CUSTOM_VIEW                    = 135;
112    private static final int HIDE_CUSTOM_VIEW                    = 136;
113
114    // Message triggered by the client to resume execution
115    private static final int NOTIFY                              = 200;
116
117    // Result transportation object for returning results across thread
118    // boundaries.
119    private static class ResultTransport<E> {
120        // Private result object
121        private E mResult;
122
123        public ResultTransport(E defaultResult) {
124            mResult = defaultResult;
125        }
126
127        public synchronized void setResult(E result) {
128            mResult = result;
129        }
130
131        public synchronized E getResult() {
132            return mResult;
133        }
134    }
135
136    /**
137     * Construct a new CallbackProxy.
138     */
139    public CallbackProxy(Context context, WebView w) {
140        // Used to start a default activity.
141        mContext = context;
142        mWebView = w;
143        mBackForwardList = new WebBackForwardList();
144    }
145
146    /**
147     * Set the WebViewClient.
148     * @param client An implementation of WebViewClient.
149     */
150    public void setWebViewClient(WebViewClient client) {
151        mWebViewClient = client;
152    }
153
154    /**
155     * Get the WebViewClient.
156     * @return the current WebViewClient instance.
157     *
158     *@hide pending API council approval.
159     */
160    public WebViewClient getWebViewClient() {
161       return mWebViewClient;
162    }
163
164    /**
165     * Set the WebChromeClient.
166     * @param client An implementation of WebChromeClient.
167     */
168    public void setWebChromeClient(WebChromeClient client) {
169        mWebChromeClient = client;
170    }
171
172    /**
173     * Get the WebChromeClient.
174     * @return the current WebChromeClient instance.
175     */
176    public WebChromeClient getWebChromeClient() {
177       return mWebChromeClient;
178    }
179
180    /**
181     * Set the client DownloadListener.
182     * @param client An implementation of DownloadListener.
183     */
184    public void setDownloadListener(DownloadListener client) {
185        mDownloadListener = client;
186    }
187
188    /**
189     * Get the Back/Forward list to return to the user or to update the cached
190     * history list.
191     */
192    public WebBackForwardList getBackForwardList() {
193        return mBackForwardList;
194    }
195
196    /**
197     * Called by the UI side.  Calling overrideUrlLoading from the WebCore
198     * side will post a message to call this method.
199     */
200    public boolean uiOverrideUrlLoading(String overrideUrl) {
201        if (overrideUrl == null || overrideUrl.length() == 0) {
202            return false;
203        }
204        boolean override = false;
205        if (mWebViewClient != null) {
206            override = mWebViewClient.shouldOverrideUrlLoading(mWebView,
207                    overrideUrl);
208        } else {
209            Intent intent = new Intent(Intent.ACTION_VIEW,
210                    Uri.parse(overrideUrl));
211            intent.addCategory(Intent.CATEGORY_BROWSABLE);
212            // If another application is running a WebView and launches the
213            // Browser through this Intent, we want to reuse the same window if
214            // possible.
215            intent.putExtra(Browser.EXTRA_APPLICATION_ID,
216                    mContext.getPackageName());
217            try {
218                mContext.startActivity(intent);
219                override = true;
220            } catch (ActivityNotFoundException ex) {
221                // If no application can handle the URL, assume that the
222                // browser can handle it.
223            }
224        }
225        return override;
226    }
227
228    /**
229     * Called by UI side.
230     */
231    public boolean uiOverrideKeyEvent(KeyEvent event) {
232        if (mWebViewClient != null) {
233            return mWebViewClient.shouldOverrideKeyEvent(mWebView, event);
234        }
235        return false;
236    }
237
238    @Override
239    public void handleMessage(Message msg) {
240        // We don't have to do synchronization because this function operates
241        // in the UI thread. The WebViewClient and WebChromeClient functions
242        // that check for a non-null callback are ok because java ensures atomic
243        // 32-bit reads and writes.
244        switch (msg.what) {
245            case PAGE_STARTED:
246                if (mWebViewClient != null) {
247                    mWebViewClient.onPageStarted(mWebView,
248                            msg.getData().getString("url"),
249                            (Bitmap) msg.obj);
250                }
251                break;
252
253            case PAGE_FINISHED:
254                String finishedUrl = (String) msg.obj;
255                mWebView.onPageFinished(finishedUrl);
256                if (mWebViewClient != null) {
257                    mWebViewClient.onPageFinished(mWebView, finishedUrl);
258                }
259                break;
260
261            case RECEIVED_ICON:
262                if (mWebChromeClient != null) {
263                    mWebChromeClient.onReceivedIcon(mWebView, (Bitmap) msg.obj);
264                }
265                break;
266
267            case RECEIVED_TOUCH_ICON_URL:
268                if (mWebChromeClient != null) {
269                    mWebChromeClient.onReceivedTouchIconUrl(mWebView,
270                            (String) msg.obj, msg.arg1 == 1);
271                }
272                break;
273
274            case RECEIVED_TITLE:
275                if (mWebChromeClient != null) {
276                    mWebChromeClient.onReceivedTitle(mWebView,
277                            (String) msg.obj);
278                }
279                break;
280
281            case TOO_MANY_REDIRECTS:
282                Message cancelMsg =
283                        (Message) msg.getData().getParcelable("cancelMsg");
284                Message continueMsg =
285                        (Message) msg.getData().getParcelable("continueMsg");
286                if (mWebViewClient != null) {
287                    mWebViewClient.onTooManyRedirects(mWebView, cancelMsg,
288                            continueMsg);
289                } else {
290                    cancelMsg.sendToTarget();
291                }
292                break;
293
294            case REPORT_ERROR:
295                if (mWebViewClient != null) {
296                    int reasonCode = msg.arg1;
297                    final String description  = msg.getData().getString("description");
298                    final String failUrl  = msg.getData().getString("failingUrl");
299                    mWebViewClient.onReceivedError(mWebView, reasonCode,
300                            description, failUrl);
301                }
302                break;
303
304            case RESEND_POST_DATA:
305                Message resend =
306                        (Message) msg.getData().getParcelable("resend");
307                Message dontResend =
308                        (Message) msg.getData().getParcelable("dontResend");
309                if (mWebViewClient != null) {
310                    mWebViewClient.onFormResubmission(mWebView, dontResend,
311                            resend);
312                } else {
313                    dontResend.sendToTarget();
314                }
315                break;
316
317            case OVERRIDE_URL:
318                String overrideUrl = msg.getData().getString("url");
319                boolean override = uiOverrideUrlLoading(overrideUrl);
320                ResultTransport<Boolean> result =
321                        (ResultTransport<Boolean>) msg.obj;
322                synchronized (this) {
323                    result.setResult(override);
324                    notify();
325                }
326                break;
327
328            case AUTH_REQUEST:
329                if (mWebViewClient != null) {
330                    HttpAuthHandler handler = (HttpAuthHandler) msg.obj;
331                    String host = msg.getData().getString("host");
332                    String realm = msg.getData().getString("realm");
333                    mWebViewClient.onReceivedHttpAuthRequest(mWebView, handler,
334                            host, realm);
335                }
336                break;
337
338            case SSL_ERROR:
339                if (mWebViewClient != null) {
340                    HashMap<String, Object> map =
341                        (HashMap<String, Object>) msg.obj;
342                    mWebViewClient.onReceivedSslError(mWebView,
343                            (SslErrorHandler) map.get("handler"),
344                            (SslError) map.get("error"));
345                }
346                break;
347
348            case PROGRESS:
349                // Synchronize to ensure mLatestProgress is not modified after
350                // setProgress is called and before mProgressUpdatePending is
351                // changed.
352                synchronized (this) {
353                    if (mWebChromeClient != null) {
354                        mWebChromeClient.onProgressChanged(mWebView,
355                                mLatestProgress);
356                    }
357                    mProgressUpdatePending = false;
358                }
359                break;
360
361            case UPDATE_VISITED:
362                if (mWebViewClient != null) {
363                    mWebViewClient.doUpdateVisitedHistory(mWebView,
364                            (String) msg.obj, msg.arg1 != 0);
365                }
366                break;
367
368            case LOAD_RESOURCE:
369                if (mWebViewClient != null) {
370                    mWebViewClient.onLoadResource(mWebView, (String) msg.obj);
371                }
372                break;
373
374            case DOWNLOAD_FILE:
375                if (mDownloadListener != null) {
376                    String url = msg.getData().getString("url");
377                    String userAgent = msg.getData().getString("userAgent");
378                    String contentDisposition =
379                        msg.getData().getString("contentDisposition");
380                    String mimetype = msg.getData().getString("mimetype");
381                    Long contentLength = msg.getData().getLong("contentLength");
382
383                    mDownloadListener.onDownloadStart(url, userAgent,
384                            contentDisposition, mimetype, contentLength);
385                }
386                break;
387
388            case CREATE_WINDOW:
389                if (mWebChromeClient != null) {
390                    if (!mWebChromeClient.onCreateWindow(mWebView,
391                                msg.arg1 == 1, msg.arg2 == 1,
392                                (Message) msg.obj)) {
393                        synchronized (this) {
394                            notify();
395                        }
396                    }
397                }
398                break;
399
400            case REQUEST_FOCUS:
401                if (mWebChromeClient != null) {
402                    mWebChromeClient.onRequestFocus(mWebView);
403                }
404                break;
405
406            case CLOSE_WINDOW:
407                if (mWebChromeClient != null) {
408                    mWebChromeClient.onCloseWindow((WebView) msg.obj);
409                }
410                break;
411
412            case SAVE_PASSWORD:
413                Bundle bundle = msg.getData();
414                String schemePlusHost = bundle.getString("host");
415                String username = bundle.getString("username");
416                String password = bundle.getString("password");
417                // If the client returned false it means that the notify message
418                // will not be sent and we should notify WebCore ourselves.
419                if (!mWebView.onSavePassword(schemePlusHost, username, password,
420                            (Message) msg.obj)) {
421                    synchronized (this) {
422                        notify();
423                    }
424                }
425                break;
426
427            case ASYNC_KEYEVENTS:
428                if (mWebViewClient != null) {
429                    mWebViewClient.onUnhandledKeyEvent(mWebView,
430                            (KeyEvent) msg.obj);
431                }
432                break;
433
434            case EXCEEDED_DATABASE_QUOTA:
435                if (mWebChromeClient != null) {
436                    HashMap<String, Object> map =
437                            (HashMap<String, Object>) msg.obj;
438                    String databaseIdentifier =
439                            (String) map.get("databaseIdentifier");
440                    String url = (String) map.get("url");
441                    long currentQuota =
442                            ((Long) map.get("currentQuota")).longValue();
443                    long totalUsedQuota =
444                            ((Long) map.get("totalUsedQuota")).longValue();
445                    long estimatedSize =
446                            ((Long) map.get("estimatedSize")).longValue();
447                    WebStorage.QuotaUpdater quotaUpdater =
448                        (WebStorage.QuotaUpdater) map.get("quotaUpdater");
449
450                    mWebChromeClient.onExceededDatabaseQuota(url,
451                            databaseIdentifier, currentQuota, estimatedSize,
452                            totalUsedQuota, quotaUpdater);
453                }
454                break;
455
456            case REACHED_APPCACHE_MAXSIZE:
457                if (mWebChromeClient != null) {
458                    HashMap<String, Object> map =
459                            (HashMap<String, Object>) msg.obj;
460                    long spaceNeeded =
461                            ((Long) map.get("spaceNeeded")).longValue();
462                    long totalUsedQuota =
463                        ((Long) map.get("totalUsedQuota")).longValue();
464                    WebStorage.QuotaUpdater quotaUpdater =
465                        (WebStorage.QuotaUpdater) map.get("quotaUpdater");
466
467                    mWebChromeClient.onReachedMaxAppCacheSize(spaceNeeded,
468                            totalUsedQuota, quotaUpdater);
469                }
470                break;
471
472            case GEOLOCATION_PERMISSIONS_SHOW_PROMPT:
473                if (mWebChromeClient != null) {
474                    HashMap<String, Object> map =
475                            (HashMap<String, Object>) msg.obj;
476                    String origin = (String) map.get("origin");
477                    GeolocationPermissions.Callback callback =
478                            (GeolocationPermissions.Callback)
479                            map.get("callback");
480                    mWebChromeClient.onGeolocationPermissionsShowPrompt(origin,
481                            callback);
482                }
483                break;
484
485            case GEOLOCATION_PERMISSIONS_HIDE_PROMPT:
486                if (mWebChromeClient != null) {
487                    mWebChromeClient.onGeolocationPermissionsHidePrompt();
488                }
489                break;
490
491            case JS_ALERT:
492                if (mWebChromeClient != null) {
493                    final JsResult res = (JsResult) msg.obj;
494                    String message = msg.getData().getString("message");
495                    String url = msg.getData().getString("url");
496                    if (!mWebChromeClient.onJsAlert(mWebView, url, message,
497                            res)) {
498                        new AlertDialog.Builder(mContext)
499                                .setTitle(getJsDialogTitle(url))
500                                .setMessage(message)
501                                .setPositiveButton(R.string.ok,
502                                        new AlertDialog.OnClickListener() {
503                                            public void onClick(
504                                                    DialogInterface dialog,
505                                                    int which) {
506                                                res.confirm();
507                                            }
508                                        })
509                                .setCancelable(false)
510                                .show();
511                    }
512                    res.setReady();
513                }
514                break;
515
516            case JS_CONFIRM:
517                if (mWebChromeClient != null) {
518                    final JsResult res = (JsResult) msg.obj;
519                    String message = msg.getData().getString("message");
520                    String url = msg.getData().getString("url");
521                    if (!mWebChromeClient.onJsConfirm(mWebView, url, message,
522                            res)) {
523                        new AlertDialog.Builder(mContext)
524                                .setTitle(getJsDialogTitle(url))
525                                .setMessage(message)
526                                .setPositiveButton(R.string.ok,
527                                        new DialogInterface.OnClickListener() {
528                                            public void onClick(
529                                                    DialogInterface dialog,
530                                                    int which) {
531                                                res.confirm();
532                                            }})
533                                .setNegativeButton(R.string.cancel,
534                                        new DialogInterface.OnClickListener() {
535                                            public void onClick(
536                                                    DialogInterface dialog,
537                                                    int which) {
538                                                res.cancel();
539                                            }})
540                                .show();
541                    }
542                    // Tell the JsResult that it is ready for client
543                    // interaction.
544                    res.setReady();
545                }
546                break;
547
548            case JS_PROMPT:
549                if (mWebChromeClient != null) {
550                    final JsPromptResult res = (JsPromptResult) msg.obj;
551                    String message = msg.getData().getString("message");
552                    String defaultVal = msg.getData().getString("default");
553                    String url = msg.getData().getString("url");
554                    if (!mWebChromeClient.onJsPrompt(mWebView, url, message,
555                                defaultVal, res)) {
556                        final LayoutInflater factory = LayoutInflater
557                                .from(mContext);
558                        final View view = factory.inflate(R.layout.js_prompt,
559                                null);
560                        final EditText v = (EditText) view
561                                .findViewById(R.id.value);
562                        v.setText(defaultVal);
563                        ((TextView) view.findViewById(R.id.message))
564                                .setText(message);
565                        new AlertDialog.Builder(mContext)
566                                .setTitle(getJsDialogTitle(url))
567                                .setView(view)
568                                .setPositiveButton(R.string.ok,
569                                        new DialogInterface.OnClickListener() {
570                                            public void onClick(
571                                                    DialogInterface dialog,
572                                                    int whichButton) {
573                                                res.confirm(v.getText()
574                                                        .toString());
575                                            }
576                                        })
577                                .setNegativeButton(R.string.cancel,
578                                        new DialogInterface.OnClickListener() {
579                                            public void onClick(
580                                                    DialogInterface dialog,
581                                                    int whichButton) {
582                                                res.cancel();
583                                            }
584                                        })
585                                .setOnCancelListener(
586                                        new DialogInterface.OnCancelListener() {
587                                            public void onCancel(
588                                                    DialogInterface dialog) {
589                                                res.cancel();
590                                            }
591                                        })
592                                .show();
593                    }
594                    // Tell the JsResult that it is ready for client
595                    // interaction.
596                    res.setReady();
597                }
598                break;
599
600            case JS_UNLOAD:
601                if (mWebChromeClient != null) {
602                    final JsResult res = (JsResult) msg.obj;
603                    String message = msg.getData().getString("message");
604                    String url = msg.getData().getString("url");
605                    if (!mWebChromeClient.onJsBeforeUnload(mWebView, url,
606                            message, res)) {
607                        final String m = mContext.getString(
608                                R.string.js_dialog_before_unload, message);
609                        new AlertDialog.Builder(mContext)
610                                .setMessage(m)
611                                .setPositiveButton(R.string.ok,
612                                        new DialogInterface.OnClickListener() {
613                                            public void onClick(
614                                                    DialogInterface dialog,
615                                                    int which) {
616                                                res.confirm();
617                                            }
618                                        })
619                                .setNegativeButton(R.string.cancel,
620                                        new DialogInterface.OnClickListener() {
621                                            public void onClick(
622                                                    DialogInterface dialog,
623                                                    int which) {
624                                                res.cancel();
625                                            }
626                                        })
627                                .show();
628                    }
629                    res.setReady();
630                }
631                break;
632
633            case JS_TIMEOUT:
634                if(mWebChromeClient != null) {
635                    final JsResult res = (JsResult) msg.obj;
636                    if(mWebChromeClient.onJsTimeout()) {
637                        res.confirm();
638                    } else {
639                        res.cancel();
640                    }
641                    res.setReady();
642                }
643                break;
644
645            case RECEIVED_CERTIFICATE:
646                mWebView.setCertificate((SslCertificate) msg.obj);
647                break;
648
649            case NOTIFY:
650                synchronized (this) {
651                    notify();
652                }
653                break;
654
655            case SCALE_CHANGED:
656                if (mWebViewClient != null) {
657                    mWebViewClient.onScaleChanged(mWebView, msg.getData()
658                            .getFloat("old"), msg.getData().getFloat("new"));
659                }
660                break;
661
662            case SWITCH_OUT_HISTORY:
663                mWebView.switchOutDrawHistory();
664                break;
665
666            case ADD_MESSAGE_TO_CONSOLE:
667                String message = msg.getData().getString("message");
668                String sourceID = msg.getData().getString("sourceID");
669                int lineNumber = msg.getData().getInt("lineNumber");
670                mWebChromeClient.onConsoleMessage(message, lineNumber, sourceID);
671                break;
672
673            case GET_VISITED_HISTORY:
674                if (mWebChromeClient != null) {
675                    mWebChromeClient.getVisitedHistory((ValueCallback<String[]>)msg.obj);
676                }
677                break;
678
679            case OPEN_FILE_CHOOSER:
680                if (mWebChromeClient != null) {
681                    mWebChromeClient.openFileChooser((UploadFile) msg.obj);
682                }
683                break;
684
685            case SHOW_CUSTOM_VIEW:
686                if (mWebChromeClient != null) {
687                    HashMap<String, Object> map =
688                            (HashMap<String, Object>) msg.obj;
689                    View view = (View) map.get("view");
690                    WebChromeClient.CustomViewCallback callback =
691                            (WebChromeClient.CustomViewCallback) map.get("callback");
692                    mWebChromeClient.onShowCustomView(view, callback);
693                }
694                break;
695
696            case HIDE_CUSTOM_VIEW:
697                if (mWebChromeClient != null) {
698                    mWebChromeClient.onHideCustomView();
699                }
700                break;
701        }
702    }
703
704    /**
705     * Return the latest progress.
706     */
707    public int getProgress() {
708        return mLatestProgress;
709    }
710
711    /**
712     * Called by WebCore side to switch out of history Picture drawing mode
713     */
714    void switchOutDrawHistory() {
715        sendMessage(obtainMessage(SWITCH_OUT_HISTORY));
716    }
717
718    private String getJsDialogTitle(String url) {
719        String title = url;
720        if (URLUtil.isDataUrl(url)) {
721            // For data: urls, we just display 'JavaScript' similar to Safari.
722            title = mContext.getString(R.string.js_dialog_title_default);
723        } else {
724            try {
725                URL aUrl = new URL(url);
726                // For example: "The page at 'http://www.mit.edu' says:"
727                title = mContext.getString(R.string.js_dialog_title,
728                        aUrl.getProtocol() + "://" + aUrl.getHost());
729            } catch (MalformedURLException ex) {
730                // do nothing. just use the url as the title
731            }
732        }
733        return title;
734    }
735
736    //--------------------------------------------------------------------------
737    // WebViewClient functions.
738    // NOTE: shouldOverrideKeyEvent is never called from the WebCore thread so
739    // it is not necessary to include it here.
740    //--------------------------------------------------------------------------
741
742    // Performance probe
743    private static final boolean PERF_PROBE = false;
744    private long mWebCoreThreadTime;
745    private long mWebCoreIdleTime;
746
747    /*
748     * If PERF_PROBE is true, this block needs to be added to MessageQueue.java.
749     * startWait() and finishWait() should be called before and after wait().
750
751    private WaitCallback mWaitCallback = null;
752    public static interface WaitCallback {
753        void startWait();
754        void finishWait();
755    }
756    public final void setWaitCallback(WaitCallback callback) {
757        mWaitCallback = callback;
758    }
759    */
760
761    // un-comment this block if PERF_PROBE is true
762    /*
763    private IdleCallback mIdleCallback = new IdleCallback();
764
765    private final class IdleCallback implements MessageQueue.WaitCallback {
766        private long mStartTime = 0;
767
768        public void finishWait() {
769            mWebCoreIdleTime += SystemClock.uptimeMillis() - mStartTime;
770        }
771
772        public void startWait() {
773            mStartTime = SystemClock.uptimeMillis();
774        }
775    }
776    */
777
778    public void onPageStarted(String url, Bitmap favicon) {
779        // Do an unsynchronized quick check to avoid posting if no callback has
780        // been set.
781        if (mWebViewClient == null) {
782            return;
783        }
784        // Performance probe
785        if (PERF_PROBE) {
786            mWebCoreThreadTime = SystemClock.currentThreadTimeMillis();
787            mWebCoreIdleTime = 0;
788            Network.getInstance(mContext).startTiming();
789            // un-comment this if PERF_PROBE is true
790//            Looper.myQueue().setWaitCallback(mIdleCallback);
791        }
792        Message msg = obtainMessage(PAGE_STARTED);
793        msg.obj = favicon;
794        msg.getData().putString("url", url);
795        sendMessage(msg);
796    }
797
798    public void onPageFinished(String url) {
799        // Performance probe
800        if (PERF_PROBE) {
801            // un-comment this if PERF_PROBE is true
802//            Looper.myQueue().setWaitCallback(null);
803            Log.d("WebCore", "WebCore thread used " +
804                    (SystemClock.currentThreadTimeMillis() - mWebCoreThreadTime)
805                    + " ms and idled " + mWebCoreIdleTime + " ms");
806            Network.getInstance(mContext).stopTiming();
807        }
808        Message msg = obtainMessage(PAGE_FINISHED, url);
809        sendMessage(msg);
810    }
811
812    public void onTooManyRedirects(Message cancelMsg, Message continueMsg) {
813        // Do an unsynchronized quick check to avoid posting if no callback has
814        // been set.
815        if (mWebViewClient == null) {
816            cancelMsg.sendToTarget();
817            return;
818        }
819
820        Message msg = obtainMessage(TOO_MANY_REDIRECTS);
821        Bundle bundle = msg.getData();
822        bundle.putParcelable("cancelMsg", cancelMsg);
823        bundle.putParcelable("continueMsg", continueMsg);
824        sendMessage(msg);
825    }
826
827    public void onReceivedError(int errorCode, String description,
828            String failingUrl) {
829        // Do an unsynchronized quick check to avoid posting if no callback has
830        // been set.
831        if (mWebViewClient == null) {
832            return;
833        }
834
835        Message msg = obtainMessage(REPORT_ERROR);
836        msg.arg1 = errorCode;
837        msg.getData().putString("description", description);
838        msg.getData().putString("failingUrl", failingUrl);
839        sendMessage(msg);
840    }
841
842    public void onFormResubmission(Message dontResend,
843            Message resend) {
844        // Do an unsynchronized quick check to avoid posting if no callback has
845        // been set.
846        if (mWebViewClient == null) {
847            dontResend.sendToTarget();
848            return;
849        }
850
851        Message msg = obtainMessage(RESEND_POST_DATA);
852        Bundle bundle = msg.getData();
853        bundle.putParcelable("resend", resend);
854        bundle.putParcelable("dontResend", dontResend);
855        sendMessage(msg);
856    }
857
858    /**
859     * Called by the WebCore side
860     */
861    public boolean shouldOverrideUrlLoading(String url) {
862        // We have a default behavior if no client exists so always send the
863        // message.
864        ResultTransport<Boolean> res = new ResultTransport<Boolean>(false);
865        Message msg = obtainMessage(OVERRIDE_URL);
866        msg.getData().putString("url", url);
867        msg.obj = res;
868        synchronized (this) {
869            sendMessage(msg);
870            try {
871                wait();
872            } catch (InterruptedException e) {
873                Log.e(LOGTAG, "Caught exception while waiting for overrideUrl");
874                Log.e(LOGTAG, Log.getStackTraceString(e));
875            }
876        }
877        return res.getResult().booleanValue();
878    }
879
880    public void onReceivedHttpAuthRequest(HttpAuthHandler handler,
881            String hostName, String realmName) {
882        // Do an unsynchronized quick check to avoid posting if no callback has
883        // been set.
884        if (mWebViewClient == null) {
885            handler.cancel();
886            return;
887        }
888        Message msg = obtainMessage(AUTH_REQUEST, handler);
889        msg.getData().putString("host", hostName);
890        msg.getData().putString("realm", realmName);
891        sendMessage(msg);
892    }
893    /**
894     * @hide - hide this because it contains a parameter of type SslError.
895     * SslError is located in a hidden package.
896     */
897    public void onReceivedSslError(SslErrorHandler handler, SslError error) {
898        // Do an unsynchronized quick check to avoid posting if no callback has
899        // been set.
900        if (mWebViewClient == null) {
901            handler.cancel();
902            return;
903        }
904        Message msg = obtainMessage(SSL_ERROR);
905        //, handler);
906        HashMap<String, Object> map = new HashMap();
907        map.put("handler", handler);
908        map.put("error", error);
909        msg.obj = map;
910        sendMessage(msg);
911    }
912    /**
913     * @hide - hide this because it contains a parameter of type SslCertificate,
914     * which is located in a hidden package.
915     */
916
917    public void onReceivedCertificate(SslCertificate certificate) {
918        // Do an unsynchronized quick check to avoid posting if no callback has
919        // been set.
920        if (mWebViewClient == null) {
921            return;
922        }
923        // here, certificate can be null (if the site is not secure)
924        sendMessage(obtainMessage(RECEIVED_CERTIFICATE, certificate));
925    }
926
927    public void doUpdateVisitedHistory(String url, boolean isReload) {
928        // Do an unsynchronized quick check to avoid posting if no callback has
929        // been set.
930        if (mWebViewClient == null) {
931            return;
932        }
933        sendMessage(obtainMessage(UPDATE_VISITED, isReload ? 1 : 0, 0, url));
934    }
935
936    public void onLoadResource(String url) {
937        // Do an unsynchronized quick check to avoid posting if no callback has
938        // been set.
939        if (mWebViewClient == null) {
940            return;
941        }
942        sendMessage(obtainMessage(LOAD_RESOURCE, url));
943    }
944
945    public void onUnhandledKeyEvent(KeyEvent event) {
946        // Do an unsynchronized quick check to avoid posting if no callback has
947        // been set.
948        if (mWebViewClient == null) {
949            return;
950        }
951        sendMessage(obtainMessage(ASYNC_KEYEVENTS, event));
952    }
953
954    public void onScaleChanged(float oldScale, float newScale) {
955        // Do an unsynchronized quick check to avoid posting if no callback has
956        // been set.
957        if (mWebViewClient == null) {
958            return;
959        }
960        Message msg = obtainMessage(SCALE_CHANGED);
961        Bundle bundle = msg.getData();
962        bundle.putFloat("old", oldScale);
963        bundle.putFloat("new", newScale);
964        sendMessage(msg);
965    }
966
967    //--------------------------------------------------------------------------
968    // DownloadListener functions.
969    //--------------------------------------------------------------------------
970
971    /**
972     * Starts a download if a download listener has been registered, otherwise
973     * return false.
974     */
975    public boolean onDownloadStart(String url, String userAgent,
976            String contentDisposition, String mimetype, long contentLength) {
977        // Do an unsynchronized quick check to avoid posting if no callback has
978        // been set.
979        if (mDownloadListener == null) {
980            // Cancel the download if there is no browser client.
981            return false;
982        }
983
984        Message msg = obtainMessage(DOWNLOAD_FILE);
985        Bundle bundle = msg.getData();
986        bundle.putString("url", url);
987        bundle.putString("userAgent", userAgent);
988        bundle.putString("mimetype", mimetype);
989        bundle.putLong("contentLength", contentLength);
990        bundle.putString("contentDisposition", contentDisposition);
991        sendMessage(msg);
992        return true;
993    }
994
995
996    //--------------------------------------------------------------------------
997    // WebView specific functions that do not interact with a client. These
998    // functions just need to operate within the UI thread.
999    //--------------------------------------------------------------------------
1000
1001    public boolean onSavePassword(String schemePlusHost, String username,
1002            String password, Message resumeMsg) {
1003        // resumeMsg should be null at this point because we want to create it
1004        // within the CallbackProxy.
1005        if (DebugFlags.CALLBACK_PROXY) {
1006            junit.framework.Assert.assertNull(resumeMsg);
1007        }
1008        resumeMsg = obtainMessage(NOTIFY);
1009
1010        Message msg = obtainMessage(SAVE_PASSWORD, resumeMsg);
1011        Bundle bundle = msg.getData();
1012        bundle.putString("host", schemePlusHost);
1013        bundle.putString("username", username);
1014        bundle.putString("password", password);
1015        synchronized (this) {
1016            sendMessage(msg);
1017            try {
1018                wait();
1019            } catch (InterruptedException e) {
1020                Log.e(LOGTAG,
1021                        "Caught exception while waiting for onSavePassword");
1022                Log.e(LOGTAG, Log.getStackTraceString(e));
1023            }
1024        }
1025        // Doesn't matter here
1026        return false;
1027    }
1028
1029    //--------------------------------------------------------------------------
1030    // WebChromeClient methods
1031    //--------------------------------------------------------------------------
1032
1033    public void onProgressChanged(int newProgress) {
1034        // Synchronize so that mLatestProgress is up-to-date.
1035        synchronized (this) {
1036            if (mWebChromeClient == null || mLatestProgress == newProgress) {
1037                return;
1038            }
1039            mLatestProgress = newProgress;
1040            if (!mProgressUpdatePending) {
1041                sendEmptyMessage(PROGRESS);
1042                mProgressUpdatePending = true;
1043            }
1044        }
1045    }
1046
1047    public WebView createWindow(boolean dialog, boolean userGesture) {
1048        // Do an unsynchronized quick check to avoid posting if no callback has
1049        // been set.
1050        if (mWebChromeClient == null) {
1051            return null;
1052        }
1053
1054        WebView.WebViewTransport transport = mWebView.new WebViewTransport();
1055        final Message msg = obtainMessage(NOTIFY);
1056        msg.obj = transport;
1057        synchronized (this) {
1058            sendMessage(obtainMessage(CREATE_WINDOW, dialog ? 1 : 0,
1059                    userGesture ? 1 : 0, msg));
1060            try {
1061                wait();
1062            } catch (InterruptedException e) {
1063                Log.e(LOGTAG,
1064                        "Caught exception while waiting for createWindow");
1065                Log.e(LOGTAG, Log.getStackTraceString(e));
1066            }
1067        }
1068
1069        WebView w = transport.getWebView();
1070        if (w != null) {
1071            w.getWebViewCore().initializeSubwindow();
1072        }
1073        return w;
1074    }
1075
1076    public void onRequestFocus() {
1077        // Do an unsynchronized quick check to avoid posting if no callback has
1078        // been set.
1079        if (mWebChromeClient == null) {
1080            return;
1081        }
1082
1083        sendEmptyMessage(REQUEST_FOCUS);
1084    }
1085
1086    public void onCloseWindow(WebView window) {
1087        // Do an unsynchronized quick check to avoid posting if no callback has
1088        // been set.
1089        if (mWebChromeClient == null) {
1090            return;
1091        }
1092        sendMessage(obtainMessage(CLOSE_WINDOW, window));
1093    }
1094
1095    public void onReceivedIcon(Bitmap icon) {
1096        // The current item might be null if the icon was already stored in the
1097        // database and this is a new WebView.
1098        WebHistoryItem i = mBackForwardList.getCurrentItem();
1099        if (i != null) {
1100            i.setFavicon(icon);
1101        }
1102        // Do an unsynchronized quick check to avoid posting if no callback has
1103        // been set.
1104        if (mWebChromeClient == null) {
1105            return;
1106        }
1107        sendMessage(obtainMessage(RECEIVED_ICON, icon));
1108    }
1109
1110    /* package */ void onReceivedTouchIconUrl(String url, boolean precomposed) {
1111        // We should have a current item but we do not want to crash so check
1112        // for null.
1113        WebHistoryItem i = mBackForwardList.getCurrentItem();
1114        if (i != null) {
1115            if (precomposed || i.getTouchIconUrl() != null) {
1116                i.setTouchIconUrl(url);
1117            }
1118        }
1119        // Do an unsynchronized quick check to avoid posting if no callback has
1120        // been set.
1121        if (mWebChromeClient == null) {
1122            return;
1123        }
1124        sendMessage(obtainMessage(RECEIVED_TOUCH_ICON_URL,
1125                precomposed ? 1 : 0, 0, url));
1126    }
1127
1128    public void onReceivedTitle(String title) {
1129        // Do an unsynchronized quick check to avoid posting if no callback has
1130        // been set.
1131        if (mWebChromeClient == null) {
1132            return;
1133        }
1134        sendMessage(obtainMessage(RECEIVED_TITLE, title));
1135    }
1136
1137    public void onJsAlert(String url, String message) {
1138        // Do an unsynchronized quick check to avoid posting if no callback has
1139        // been set.
1140        if (mWebChromeClient == null) {
1141            return;
1142        }
1143        JsResult result = new JsResult(this, false);
1144        Message alert = obtainMessage(JS_ALERT, result);
1145        alert.getData().putString("message", message);
1146        alert.getData().putString("url", url);
1147        synchronized (this) {
1148            sendMessage(alert);
1149            try {
1150                wait();
1151            } catch (InterruptedException e) {
1152                Log.e(LOGTAG, "Caught exception while waiting for jsAlert");
1153                Log.e(LOGTAG, Log.getStackTraceString(e));
1154            }
1155        }
1156    }
1157
1158    public boolean onJsConfirm(String url, String message) {
1159        // Do an unsynchronized quick check to avoid posting if no callback has
1160        // been set.
1161        if (mWebChromeClient == null) {
1162            return false;
1163        }
1164        JsResult result = new JsResult(this, false);
1165        Message confirm = obtainMessage(JS_CONFIRM, result);
1166        confirm.getData().putString("message", message);
1167        confirm.getData().putString("url", url);
1168        synchronized (this) {
1169            sendMessage(confirm);
1170            try {
1171                wait();
1172            } catch (InterruptedException e) {
1173                Log.e(LOGTAG, "Caught exception while waiting for jsConfirm");
1174                Log.e(LOGTAG, Log.getStackTraceString(e));
1175            }
1176        }
1177        return result.getResult();
1178    }
1179
1180    public String onJsPrompt(String url, String message, String defaultValue) {
1181        // Do an unsynchronized quick check to avoid posting if no callback has
1182        // been set.
1183        if (mWebChromeClient == null) {
1184            return null;
1185        }
1186        JsPromptResult result = new JsPromptResult(this);
1187        Message prompt = obtainMessage(JS_PROMPT, result);
1188        prompt.getData().putString("message", message);
1189        prompt.getData().putString("default", defaultValue);
1190        prompt.getData().putString("url", url);
1191        synchronized (this) {
1192            sendMessage(prompt);
1193            try {
1194                wait();
1195            } catch (InterruptedException e) {
1196                Log.e(LOGTAG, "Caught exception while waiting for jsPrompt");
1197                Log.e(LOGTAG, Log.getStackTraceString(e));
1198            }
1199        }
1200        return result.getStringResult();
1201    }
1202
1203    public boolean onJsBeforeUnload(String url, String message) {
1204        // Do an unsynchronized quick check to avoid posting if no callback has
1205        // been set.
1206        if (mWebChromeClient == null) {
1207            return true;
1208        }
1209        JsResult result = new JsResult(this, true);
1210        Message confirm = obtainMessage(JS_UNLOAD, result);
1211        confirm.getData().putString("message", message);
1212        confirm.getData().putString("url", url);
1213        synchronized (this) {
1214            sendMessage(confirm);
1215            try {
1216                wait();
1217            } catch (InterruptedException e) {
1218                Log.e(LOGTAG, "Caught exception while waiting for jsUnload");
1219                Log.e(LOGTAG, Log.getStackTraceString(e));
1220            }
1221        }
1222        return result.getResult();
1223    }
1224
1225    /**
1226     * Called by WebViewCore to inform the Java side that the current origin
1227     * has overflowed it's database quota. Called in the WebCore thread so
1228     * posts a message to the UI thread that will prompt the WebChromeClient
1229     * for what to do. On return back to C++ side, the WebCore thread will
1230     * sleep pending a new quota value.
1231     * @param url The URL that caused the quota overflow.
1232     * @param databaseIdentifier The identifier of the database that the
1233     *     transaction that caused the overflow was running on.
1234     * @param currentQuota The current quota the origin is allowed.
1235     * @param estimatedSize The estimated size of the database.
1236     * @param totalUsedQuota is the sum of all origins' quota.
1237     * @param quotaUpdater An instance of a class encapsulating a callback
1238     *     to WebViewCore to run when the decision to allow or deny more
1239     *     quota has been made.
1240     */
1241    public void onExceededDatabaseQuota(
1242            String url, String databaseIdentifier, long currentQuota,
1243            long estimatedSize, long totalUsedQuota,
1244            WebStorage.QuotaUpdater quotaUpdater) {
1245        if (mWebChromeClient == null) {
1246            quotaUpdater.updateQuota(currentQuota);
1247            return;
1248        }
1249
1250        Message exceededQuota = obtainMessage(EXCEEDED_DATABASE_QUOTA);
1251        HashMap<String, Object> map = new HashMap();
1252        map.put("databaseIdentifier", databaseIdentifier);
1253        map.put("url", url);
1254        map.put("currentQuota", currentQuota);
1255        map.put("estimatedSize", estimatedSize);
1256        map.put("totalUsedQuota", totalUsedQuota);
1257        map.put("quotaUpdater", quotaUpdater);
1258        exceededQuota.obj = map;
1259        sendMessage(exceededQuota);
1260    }
1261
1262    /**
1263     * Called by WebViewCore to inform the Java side that the appcache has
1264     * exceeded its max size.
1265     * @param spaceNeeded is the amount of disk space that would be needed
1266     * in order for the last appcache operation to succeed.
1267     * @param totalUsedQuota is the sum of all origins' quota.
1268     * @param quotaUpdater An instance of a class encapsulating a callback
1269     * to WebViewCore to run when the decision to allow or deny a bigger
1270     * app cache size has been made.
1271     */
1272    public void onReachedMaxAppCacheSize(long spaceNeeded,
1273            long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
1274        if (mWebChromeClient == null) {
1275            quotaUpdater.updateQuota(0);
1276            return;
1277        }
1278
1279        Message msg = obtainMessage(REACHED_APPCACHE_MAXSIZE);
1280        HashMap<String, Object> map = new HashMap();
1281        map.put("spaceNeeded", spaceNeeded);
1282        map.put("totalUsedQuota", totalUsedQuota);
1283        map.put("quotaUpdater", quotaUpdater);
1284        msg.obj = map;
1285        sendMessage(msg);
1286    }
1287
1288    /**
1289     * Called by WebViewCore to instruct the browser to display a prompt to ask
1290     * the user to set the Geolocation permission state for the given origin.
1291     * @param origin The origin requesting Geolocation permsissions.
1292     * @param callback The callback to call once a permission state has been
1293     *     obtained.
1294     */
1295    public void onGeolocationPermissionsShowPrompt(String origin,
1296            GeolocationPermissions.Callback callback) {
1297        if (mWebChromeClient == null) {
1298            return;
1299        }
1300
1301        Message showMessage =
1302                obtainMessage(GEOLOCATION_PERMISSIONS_SHOW_PROMPT);
1303        HashMap<String, Object> map = new HashMap();
1304        map.put("origin", origin);
1305        map.put("callback", callback);
1306        showMessage.obj = map;
1307        sendMessage(showMessage);
1308    }
1309
1310    /**
1311     * Called by WebViewCore to instruct the browser to hide the Geolocation
1312     * permissions prompt.
1313     */
1314    public void onGeolocationPermissionsHidePrompt() {
1315        if (mWebChromeClient == null) {
1316            return;
1317        }
1318
1319        Message hideMessage = obtainMessage(GEOLOCATION_PERMISSIONS_HIDE_PROMPT);
1320        sendMessage(hideMessage);
1321    }
1322
1323    /**
1324     * Called by WebViewCore when we have a message to be added to the JavaScript
1325     * error console. Sends a message to the Java side with the details.
1326     * @param message The message to add to the console.
1327     * @param lineNumber The lineNumber of the source file on which the error
1328     *     occurred.
1329     * @param sourceID The filename of the source file in which the error
1330     *     occurred.
1331     */
1332    public void addMessageToConsole(String message, int lineNumber, String sourceID) {
1333        if (mWebChromeClient == null) {
1334            return;
1335        }
1336
1337        Message msg = obtainMessage(ADD_MESSAGE_TO_CONSOLE);
1338        msg.getData().putString("message", message);
1339        msg.getData().putString("sourceID", sourceID);
1340        msg.getData().putInt("lineNumber", lineNumber);
1341        sendMessage(msg);
1342    }
1343
1344    public boolean onJsTimeout() {
1345        //always interrupt timedout JS by default
1346        if (mWebChromeClient == null) {
1347            return true;
1348        }
1349        JsResult result = new JsResult(this, true);
1350        Message timeout = obtainMessage(JS_TIMEOUT, result);
1351        synchronized (this) {
1352            sendMessage(timeout);
1353            try {
1354                wait();
1355            } catch (InterruptedException e) {
1356                Log.e(LOGTAG, "Caught exception while waiting for jsUnload");
1357                Log.e(LOGTAG, Log.getStackTraceString(e));
1358            }
1359        }
1360        return result.getResult();
1361    }
1362
1363    public void getVisitedHistory(ValueCallback<String[]> callback) {
1364        if (mWebChromeClient == null) {
1365            return;
1366        }
1367        Message msg = obtainMessage(GET_VISITED_HISTORY);
1368        msg.obj = callback;
1369        sendMessage(msg);
1370    }
1371
1372    private class UploadFile implements ValueCallback<Uri> {
1373        private Uri mValue;
1374        public void onReceiveValue(Uri value) {
1375            mValue = value;
1376            synchronized (CallbackProxy.this) {
1377                CallbackProxy.this.notify();
1378            }
1379        }
1380        public Uri getResult() {
1381            return mValue;
1382        }
1383    }
1384
1385    /**
1386     * Called by WebViewCore to open a file chooser.
1387     */
1388    /* package */ Uri openFileChooser() {
1389        if (mWebChromeClient == null) {
1390            return null;
1391        }
1392        Message myMessage = obtainMessage(OPEN_FILE_CHOOSER);
1393        UploadFile uploadFile = new UploadFile();
1394        myMessage.obj = uploadFile;
1395        synchronized (this) {
1396            sendMessage(myMessage);
1397            try {
1398                wait();
1399            } catch (InterruptedException e) {
1400                Log.e(LOGTAG,
1401                        "Caught exception while waiting for openFileChooser");
1402                Log.e(LOGTAG, Log.getStackTraceString(e));
1403            }
1404        }
1405        return uploadFile.getResult();
1406    }
1407
1408    /* package */ void showCustomView(View view, WebChromeClient.CustomViewCallback callback) {
1409        if (mWebChromeClient == null) {
1410            return;
1411        }
1412        Message msg = obtainMessage(SHOW_CUSTOM_VIEW);
1413        HashMap<String, Object> map = new HashMap();
1414        map.put("view", view);
1415        map.put("callback", callback);
1416        msg.obj = map;
1417        sendMessage(msg);
1418    }
1419
1420    /* package */ void hideCustomView() {
1421        if (mWebChromeClient == null) {
1422            return;
1423        }
1424        Message msg = obtainMessage(HIDE_CUSTOM_VIEW);
1425        sendMessage(msg);
1426    }
1427}
1428