WebView.java revision 83d4ba83ddf8309d2c0c38d69154217d6a9c0a6c
1/*
2 * Copyright (C) 2006 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.Context;
21import android.content.DialogInterface;
22import android.content.Intent;
23import android.content.DialogInterface.OnCancelListener;
24import android.database.DataSetObserver;
25import android.graphics.Bitmap;
26import android.graphics.Canvas;
27import android.graphics.Color;
28import android.graphics.Picture;
29import android.graphics.Point;
30import android.graphics.Rect;
31import android.graphics.Region;
32import android.graphics.drawable.Drawable;
33import android.net.http.SslCertificate;
34import android.net.Uri;
35import android.os.Bundle;
36import android.os.Handler;
37import android.os.Message;
38import android.os.ServiceManager;
39import android.os.SystemClock;
40import android.provider.Checkin;
41import android.text.IClipboard;
42import android.text.Selection;
43import android.text.Spannable;
44import android.util.AttributeSet;
45import android.util.EventLog;
46import android.util.Log;
47import android.util.TypedValue;
48import android.view.Gravity;
49import android.view.KeyEvent;
50import android.view.LayoutInflater;
51import android.view.MotionEvent;
52import android.view.SoundEffectConstants;
53import android.view.VelocityTracker;
54import android.view.View;
55import android.view.ViewConfiguration;
56import android.view.ViewGroup;
57import android.view.ViewParent;
58import android.view.ViewTreeObserver;
59import android.view.animation.AlphaAnimation;
60import android.view.inputmethod.InputMethodManager;
61import android.webkit.WebTextView.AutoCompleteAdapter;
62import android.webkit.WebViewCore.EventHub;
63import android.widget.AbsoluteLayout;
64import android.widget.Adapter;
65import android.widget.AdapterView;
66import android.widget.ArrayAdapter;
67import android.widget.FrameLayout;
68import android.widget.ListView;
69import android.widget.Scroller;
70import android.widget.Toast;
71import android.widget.ZoomButtonsController;
72import android.widget.ZoomControls;
73import android.widget.AdapterView.OnItemClickListener;
74
75import java.io.File;
76import java.io.FileInputStream;
77import java.io.FileNotFoundException;
78import java.io.FileOutputStream;
79import java.io.IOException;
80import java.net.URLDecoder;
81import java.util.ArrayList;
82import java.util.List;
83import java.util.Map;
84
85/**
86 * <p>A View that displays web pages. This class is the basis upon which you
87 * can roll your own web browser or simply display some online content within your Activity.
88 * It uses the WebKit rendering engine to display
89 * web pages and includes methods to navigate forward and backward
90 * through a history, zoom in and out, perform text searches and more.</p>
91 * <p>To enable the built-in zoom, set
92 * {@link #getSettings() WebSettings}.{@link WebSettings#setBuiltInZoomControls(boolean)}
93 * (introduced in API version 3).
94 * <p>Note that, in order for your Activity to access the Internet and load web pages
95 * in a WebView, you must add the <var>INTERNET</var> permissions to your
96 * Android Manifest file:</p>
97 * <pre>&lt;uses-permission android:name="android.permission.INTERNET" /></pre>
98 *
99 * <p>This must be a child of the <code>&lt;manifest></code> element.</p>
100 *
101 * <h3>Basic usage</h3>
102 *
103 * <p>By default, a WebView provides no browser-like widgets, does not
104 * enable JavaScript and errors will be ignored. If your goal is only
105 * to display some HTML as a part of your UI, this is probably fine;
106 * the user won't need to interact with the web page beyond reading
107 * it, and the web page won't need to interact with the user. If you
108 * actually want a fully blown web browser, then you probably want to
109 * invoke the Browser application with your URL rather than show it
110 * with a WebView. See {@link android.content.Intent} for more information.</p>
111 *
112 * <pre class="prettyprint">
113 * WebView webview = new WebView(this);
114 * setContentView(webview);
115 *
116 * // Simplest usage: note that an exception will NOT be thrown
117 * // if there is an error loading this page (see below).
118 * webview.loadUrl("http://slashdot.org/");
119 *
120 * // Of course you can also load from any string:
121 * String summary = "&lt;html>&lt;body>You scored &lt;b>192</b> points.&lt;/body>&lt;/html>";
122 * webview.loadData(summary, "text/html", "utf-8");
123 * // ... although note that there are restrictions on what this HTML can do.
124 * // See the JavaDocs for loadData and loadDataWithBaseUrl for more info.
125 * </pre>
126 *
127 * <p>A WebView has several customization points where you can add your
128 * own behavior. These are:</p>
129 *
130 * <ul>
131 *   <li>Creating and setting a {@link android.webkit.WebChromeClient} subclass.
132 *       This class is called when something that might impact a
133 *       browser UI happens, for instance, progress updates and
134 *       JavaScript alerts are sent here.
135 *   </li>
136 *   <li>Creating and setting a {@link android.webkit.WebViewClient} subclass.
137 *       It will be called when things happen that impact the
138 *       rendering of the content, eg, errors or form submissions. You
139 *       can also intercept URL loading here.</li>
140 *   <li>Via the {@link android.webkit.WebSettings} class, which contains
141 *       miscellaneous configuration. </li>
142 *   <li>With the {@link android.webkit.WebView#addJavascriptInterface} method.
143 *       This lets you bind Java objects into the WebView so they can be
144 *       controlled from the web pages JavaScript.</li>
145 * </ul>
146 *
147 * <p>Here's a more complicated example, showing error handling,
148 *    settings, and progress notification:</p>
149 *
150 * <pre class="prettyprint">
151 * // Let's display the progress in the activity title bar, like the
152 * // browser app does.
153 * getWindow().requestFeature(Window.FEATURE_PROGRESS);
154 *
155 * webview.getSettings().setJavaScriptEnabled(true);
156 *
157 * final Activity activity = this;
158 * webview.setWebChromeClient(new WebChromeClient() {
159 *   public void onProgressChanged(WebView view, int progress) {
160 *     // Activities and WebViews measure progress with different scales.
161 *     // The progress meter will automatically disappear when we reach 100%
162 *     activity.setProgress(progress * 1000);
163 *   }
164 * });
165 * webview.setWebViewClient(new WebViewClient() {
166 *   public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
167 *     Toast.makeText(activity, "Oh no! " + description, Toast.LENGTH_SHORT).show();
168 *   }
169 * });
170 *
171 * webview.loadUrl("http://slashdot.org/");
172 * </pre>
173 *
174 * <h3>Cookie and window management</h3>
175 *
176 * <p>For obvious security reasons, your application has its own
177 * cache, cookie store etc - it does not share the Browser
178 * applications data. Cookies are managed on a separate thread, so
179 * operations like index building don't block the UI
180 * thread. Follow the instructions in {@link android.webkit.CookieSyncManager}
181 * if you want to use cookies in your application.
182 * </p>
183 *
184 * <p>By default, requests by the HTML to open new windows are
185 * ignored. This is true whether they be opened by JavaScript or by
186 * the target attribute on a link. You can customize your
187 * WebChromeClient to provide your own behaviour for opening multiple windows,
188 * and render them in whatever manner you want.</p>
189 *
190 * <p>Standard behavior for an Activity is to be destroyed and
191 * recreated when the devices orientation is changed. This will cause
192 * the WebView to reload the current page. If you don't want that, you
193 * can set your Activity to handle the orientation and keyboardHidden
194 * changes, and then just leave the WebView alone. It'll automatically
195 * re-orient itself as appropriate.</p>
196 */
197public class WebView extends AbsoluteLayout
198        implements ViewTreeObserver.OnGlobalFocusChangeListener,
199        ViewGroup.OnHierarchyChangeListener {
200
201    // if AUTO_REDRAW_HACK is true, then the CALL key will toggle redrawing
202    // the screen all-the-time. Good for profiling our drawing code
203    static private final boolean AUTO_REDRAW_HACK = false;
204    // true means redraw the screen all-the-time. Only with AUTO_REDRAW_HACK
205    private boolean mAutoRedraw;
206
207    static final String LOGTAG = "webview";
208
209    private static class ExtendedZoomControls extends FrameLayout {
210        public ExtendedZoomControls(Context context, AttributeSet attrs) {
211            super(context, attrs);
212            LayoutInflater inflater = (LayoutInflater)
213                    context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
214            inflater.inflate(com.android.internal.R.layout.zoom_magnify, this, true);
215            mPlusMinusZoomControls = (ZoomControls) findViewById(
216                    com.android.internal.R.id.zoomControls);
217            findViewById(com.android.internal.R.id.zoomMagnify).setVisibility(
218                    View.GONE);
219        }
220
221        public void show(boolean showZoom, boolean canZoomOut) {
222            mPlusMinusZoomControls.setVisibility(
223                    showZoom ? View.VISIBLE : View.GONE);
224            fade(View.VISIBLE, 0.0f, 1.0f);
225        }
226
227        public void hide() {
228            fade(View.GONE, 1.0f, 0.0f);
229        }
230
231        private void fade(int visibility, float startAlpha, float endAlpha) {
232            AlphaAnimation anim = new AlphaAnimation(startAlpha, endAlpha);
233            anim.setDuration(500);
234            startAnimation(anim);
235            setVisibility(visibility);
236        }
237
238        public boolean hasFocus() {
239            return mPlusMinusZoomControls.hasFocus();
240        }
241
242        public void setOnZoomInClickListener(OnClickListener listener) {
243            mPlusMinusZoomControls.setOnZoomInClickListener(listener);
244        }
245
246        public void setOnZoomOutClickListener(OnClickListener listener) {
247            mPlusMinusZoomControls.setOnZoomOutClickListener(listener);
248        }
249
250        ZoomControls    mPlusMinusZoomControls;
251    }
252
253    /**
254     *  Transportation object for returning WebView across thread boundaries.
255     */
256    public class WebViewTransport {
257        private WebView mWebview;
258
259        /**
260         * Set the WebView to the transportation object.
261         * @param webview The WebView to transport.
262         */
263        public synchronized void setWebView(WebView webview) {
264            mWebview = webview;
265        }
266
267        /**
268         * Return the WebView object.
269         * @return WebView The transported WebView object.
270         */
271        public synchronized WebView getWebView() {
272            return mWebview;
273        }
274    }
275
276    // A final CallbackProxy shared by WebViewCore and BrowserFrame.
277    private final CallbackProxy mCallbackProxy;
278
279    private final WebViewDatabase mDatabase;
280
281    // SSL certificate for the main top-level page (if secure)
282    private SslCertificate mCertificate;
283
284    // Native WebView pointer that is 0 until the native object has been
285    // created.
286    private int mNativeClass;
287    // This would be final but it needs to be set to null when the WebView is
288    // destroyed.
289    private WebViewCore mWebViewCore;
290    // Handler for dispatching UI messages.
291    /* package */ final Handler mPrivateHandler = new PrivateHandler();
292    private WebTextView mWebTextView;
293    // Used to ignore changes to webkit text that arrives to the UI side after
294    // more key events.
295    private int mTextGeneration;
296
297    // Used by WebViewCore to create child views.
298    /* package */ final ViewManager mViewManager;
299
300    /**
301     * Position of the last touch event.
302     */
303    private float mLastTouchX;
304    private float mLastTouchY;
305
306    /**
307     * Time of the last touch event.
308     */
309    private long mLastTouchTime;
310
311    /**
312     * Time of the last time sending touch event to WebViewCore
313     */
314    private long mLastSentTouchTime;
315
316    /**
317     * The minimum elapsed time before sending another ACTION_MOVE event to
318     * WebViewCore. This really should be tuned for each type of the devices.
319     * For example in Google Map api test case, it takes Dream device at least
320     * 150ms to do a full cycle in the WebViewCore by processing a touch event,
321     * triggering the layout and drawing the picture. While the same process
322     * takes 60+ms on the current high speed device. If we make
323     * TOUCH_SENT_INTERVAL too small, there will be multiple touch events sent
324     * to WebViewCore queue and the real layout and draw events will be pushed
325     * to further, which slows down the refresh rate. Choose 50 to favor the
326     * current high speed devices. For Dream like devices, 100 is a better
327     * choice. Maybe make this in the buildspec later.
328     */
329    private static final int TOUCH_SENT_INTERVAL = 50;
330
331    /**
332     * Helper class to get velocity for fling
333     */
334    VelocityTracker mVelocityTracker;
335    private int mMaximumFling;
336    private float mLastVelocity;
337    private float mLastVelX;
338    private float mLastVelY;
339
340    /**
341     * Touch mode
342     */
343    private int mTouchMode = TOUCH_DONE_MODE;
344    private static final int TOUCH_INIT_MODE = 1;
345    private static final int TOUCH_DRAG_START_MODE = 2;
346    private static final int TOUCH_DRAG_MODE = 3;
347    private static final int TOUCH_SHORTPRESS_START_MODE = 4;
348    private static final int TOUCH_SHORTPRESS_MODE = 5;
349    private static final int TOUCH_DOUBLE_TAP_MODE = 6;
350    private static final int TOUCH_DONE_MODE = 7;
351    private static final int TOUCH_SELECT_MODE = 8;
352
353    // Whether to forward the touch events to WebCore
354    private boolean mForwardTouchEvents = false;
355
356    // Whether to prevent drag during touch. The initial value depends on
357    // mForwardTouchEvents. If WebCore wants touch events, we assume it will
358    // take control of touch events unless it says no for touch down event.
359    private boolean mPreventDrag;
360
361    // To keep track of whether the current drag was initiated by a WebTextView,
362    // so that we know not to hide the cursor
363    boolean mDragFromTextInput;
364
365    // Whether or not to draw the cursor ring.
366    private boolean mDrawCursorRing = true;
367
368    // true if onPause has been called (and not onResume)
369    private boolean mIsPaused;
370
371    /**
372     * Customizable constant
373     */
374    // pre-computed square of ViewConfiguration.getScaledTouchSlop()
375    private int mTouchSlopSquare;
376    // pre-computed square of ViewConfiguration.getScaledDoubleTapSlop()
377    private int mDoubleTapSlopSquare;
378    // pre-computed density adjusted navigation slop
379    private int mNavSlop;
380    // This should be ViewConfiguration.getTapTimeout()
381    // But system time out is 100ms, which is too short for the browser.
382    // In the browser, if it switches out of tap too soon, jump tap won't work.
383    private static final int TAP_TIMEOUT = 200;
384    // This should be ViewConfiguration.getLongPressTimeout()
385    // But system time out is 500ms, which is too short for the browser.
386    // With a short timeout, it's difficult to treat trigger a short press.
387    private static final int LONG_PRESS_TIMEOUT = 1000;
388    // needed to avoid flinging after a pause of no movement
389    private static final int MIN_FLING_TIME = 250;
390    // The time that the Zoom Controls are visible before fading away
391    private static final long ZOOM_CONTROLS_TIMEOUT =
392            ViewConfiguration.getZoomControlsTimeout();
393    // The amount of content to overlap between two screens when going through
394    // pages with the space bar, in pixels.
395    private static final int PAGE_SCROLL_OVERLAP = 24;
396
397    /**
398     * These prevent calling requestLayout if either dimension is fixed. This
399     * depends on the layout parameters and the measure specs.
400     */
401    boolean mWidthCanMeasure;
402    boolean mHeightCanMeasure;
403
404    // Remember the last dimensions we sent to the native side so we can avoid
405    // sending the same dimensions more than once.
406    int mLastWidthSent;
407    int mLastHeightSent;
408
409    private int mContentWidth;   // cache of value from WebViewCore
410    private int mContentHeight;  // cache of value from WebViewCore
411
412    // Need to have the separate control for horizontal and vertical scrollbar
413    // style than the View's single scrollbar style
414    private boolean mOverlayHorizontalScrollbar = true;
415    private boolean mOverlayVerticalScrollbar = false;
416
417    // our standard speed. this way small distances will be traversed in less
418    // time than large distances, but we cap the duration, so that very large
419    // distances won't take too long to get there.
420    private static final int STD_SPEED = 480;  // pixels per second
421    // time for the longest scroll animation
422    private static final int MAX_DURATION = 750;   // milliseconds
423    private Scroller mScroller;
424
425    private boolean mWrapContent;
426
427    /**
428     * Private message ids
429     */
430    private static final int REMEMBER_PASSWORD          = 1;
431    private static final int NEVER_REMEMBER_PASSWORD    = 2;
432    private static final int SWITCH_TO_SHORTPRESS       = 3;
433    private static final int SWITCH_TO_LONGPRESS        = 4;
434    private static final int RELEASE_SINGLE_TAP         = 5;
435    private static final int REQUEST_FORM_DATA          = 6;
436    private static final int RESUME_WEBCORE_UPDATE      = 7;
437
438    //! arg1=x, arg2=y
439    static final int SCROLL_TO_MSG_ID                   = 10;
440    static final int SCROLL_BY_MSG_ID                   = 11;
441    //! arg1=x, arg2=y
442    static final int SPAWN_SCROLL_TO_MSG_ID             = 12;
443    //! arg1=x, arg2=y
444    static final int SYNC_SCROLL_TO_MSG_ID              = 13;
445    static final int NEW_PICTURE_MSG_ID                 = 14;
446    static final int UPDATE_TEXT_ENTRY_MSG_ID           = 15;
447    static final int WEBCORE_INITIALIZED_MSG_ID         = 16;
448    static final int UPDATE_TEXTFIELD_TEXT_MSG_ID       = 17;
449    static final int MOVE_OUT_OF_PLUGIN                 = 19;
450    static final int CLEAR_TEXT_ENTRY                   = 20;
451    static final int UPDATE_TEXT_SELECTION_MSG_ID       = 21;
452    static final int UPDATE_CLIPBOARD                   = 22;
453    static final int LONG_PRESS_CENTER                  = 23;
454    static final int PREVENT_TOUCH_ID                   = 24;
455    static final int WEBCORE_NEED_TOUCH_EVENTS          = 25;
456    // obj=Rect in doc coordinates
457    static final int INVAL_RECT_MSG_ID                  = 26;
458    static final int REQUEST_KEYBOARD                   = 27;
459
460    static final String[] HandlerDebugString = {
461        "REMEMBER_PASSWORD", //              = 1;
462        "NEVER_REMEMBER_PASSWORD", //        = 2;
463        "SWITCH_TO_SHORTPRESS", //           = 3;
464        "SWITCH_TO_LONGPRESS", //            = 4;
465        "RELEASE_SINGLE_TAP", //             = 5;
466        "REQUEST_FORM_DATA", //              = 6;
467        "SWITCH_TO_CLICK", //                = 7;
468        "RESUME_WEBCORE_UPDATE", //          = 8;
469        "9",
470        "SCROLL_TO_MSG_ID", //               = 10;
471        "SCROLL_BY_MSG_ID", //               = 11;
472        "SPAWN_SCROLL_TO_MSG_ID", //         = 12;
473        "SYNC_SCROLL_TO_MSG_ID", //          = 13;
474        "NEW_PICTURE_MSG_ID", //             = 14;
475        "UPDATE_TEXT_ENTRY_MSG_ID", //       = 15;
476        "WEBCORE_INITIALIZED_MSG_ID", //     = 16;
477        "UPDATE_TEXTFIELD_TEXT_MSG_ID", //   = 17;
478        "18", //        = 18;
479        "MOVE_OUT_OF_PLUGIN", //             = 19;
480        "CLEAR_TEXT_ENTRY", //               = 20;
481        "UPDATE_TEXT_SELECTION_MSG_ID", //   = 21;
482        "UPDATE_CLIPBOARD", //               = 22;
483        "LONG_PRESS_CENTER", //              = 23;
484        "PREVENT_TOUCH_ID", //               = 24;
485        "WEBCORE_NEED_TOUCH_EVENTS", //      = 25;
486        "INVAL_RECT_MSG_ID", //              = 26;
487        "REQUEST_KEYBOARD" //                = 27;
488    };
489
490    // default scale limit. Depending on the display density
491    private static float DEFAULT_MAX_ZOOM_SCALE;
492    private static float DEFAULT_MIN_ZOOM_SCALE;
493    // scale limit, which can be set through viewport meta tag in the web page
494    private float mMaxZoomScale;
495    private float mMinZoomScale;
496    private boolean mMinZoomScaleFixed = true;
497
498    // initial scale in percent. 0 means using default.
499    private int mInitialScale = 0;
500
501    // while in the zoom overview mode, the page's width is fully fit to the
502    // current window. The page is alive, in another words, you can click to
503    // follow the links. Double tap will toggle between zoom overview mode and
504    // the last zoom scale.
505    boolean mInZoomOverview = false;
506
507    // ideally mZoomOverviewWidth should be mContentWidth. But sites like espn,
508    // engadget always have wider mContentWidth no matter what viewport size is.
509    int mZoomOverviewWidth = WebViewCore.DEFAULT_VIEWPORT_WIDTH;
510    float mLastScale;
511
512    // default scale. Depending on the display density.
513    static int DEFAULT_SCALE_PERCENT;
514    private float mDefaultScale;
515
516    // set to true temporarily while the zoom control is being dragged
517    private boolean mPreviewZoomOnly = false;
518
519    // computed scale and inverse, from mZoomWidth.
520    private float mActualScale;
521    private float mInvActualScale;
522    // if this is non-zero, it is used on drawing rather than mActualScale
523    private float mZoomScale;
524    private float mInvInitialZoomScale;
525    private float mInvFinalZoomScale;
526    private int mInitialScrollX;
527    private int mInitialScrollY;
528    private long mZoomStart;
529    private static final int ZOOM_ANIMATION_LENGTH = 500;
530
531    private boolean mUserScroll = false;
532
533    private int mSnapScrollMode = SNAP_NONE;
534    private static final int SNAP_NONE = 1;
535    private static final int SNAP_X = 2;
536    private static final int SNAP_Y = 3;
537    private static final int SNAP_X_LOCK = 4;
538    private static final int SNAP_Y_LOCK = 5;
539    private boolean mSnapPositive;
540
541    // Used to match key downs and key ups
542    private boolean mGotKeyDown;
543
544    /* package */ static boolean mLogEvent = true;
545    private static final int EVENT_LOG_ZOOM_LEVEL_CHANGE = 70101;
546    private static final int EVENT_LOG_DOUBLE_TAP_DURATION = 70102;
547
548    // for event log
549    private long mLastTouchUpTime = 0;
550
551    /**
552     * URI scheme for telephone number
553     */
554    public static final String SCHEME_TEL = "tel:";
555    /**
556     * URI scheme for email address
557     */
558    public static final String SCHEME_MAILTO = "mailto:";
559    /**
560     * URI scheme for map address
561     */
562    public static final String SCHEME_GEO = "geo:0,0?q=";
563
564    private int mBackgroundColor = Color.WHITE;
565
566    // Used to notify listeners of a new picture.
567    private PictureListener mPictureListener;
568    /**
569     * Interface to listen for new pictures as they change.
570     */
571    public interface PictureListener {
572        /**
573         * Notify the listener that the picture has changed.
574         * @param view The WebView that owns the picture.
575         * @param picture The new picture.
576         */
577        public void onNewPicture(WebView view, Picture picture);
578    }
579
580    // FIXME: Want to make this public, but need to change the API file.
581    public /*static*/ class HitTestResult {
582        /**
583         * Default HitTestResult, where the target is unknown
584         */
585        public static final int UNKNOWN_TYPE = 0;
586        /**
587         * HitTestResult for hitting a HTML::a tag
588         */
589        public static final int ANCHOR_TYPE = 1;
590        /**
591         * HitTestResult for hitting a phone number
592         */
593        public static final int PHONE_TYPE = 2;
594        /**
595         * HitTestResult for hitting a map address
596         */
597        public static final int GEO_TYPE = 3;
598        /**
599         * HitTestResult for hitting an email address
600         */
601        public static final int EMAIL_TYPE = 4;
602        /**
603         * HitTestResult for hitting an HTML::img tag
604         */
605        public static final int IMAGE_TYPE = 5;
606        /**
607         * HitTestResult for hitting a HTML::a tag which contains HTML::img
608         */
609        public static final int IMAGE_ANCHOR_TYPE = 6;
610        /**
611         * HitTestResult for hitting a HTML::a tag with src=http
612         */
613        public static final int SRC_ANCHOR_TYPE = 7;
614        /**
615         * HitTestResult for hitting a HTML::a tag with src=http + HTML::img
616         */
617        public static final int SRC_IMAGE_ANCHOR_TYPE = 8;
618        /**
619         * HitTestResult for hitting an edit text area
620         */
621        public static final int EDIT_TEXT_TYPE = 9;
622
623        private int mType;
624        private String mExtra;
625
626        HitTestResult() {
627            mType = UNKNOWN_TYPE;
628        }
629
630        private void setType(int type) {
631            mType = type;
632        }
633
634        private void setExtra(String extra) {
635            mExtra = extra;
636        }
637
638        public int getType() {
639            return mType;
640        }
641
642        public String getExtra() {
643            return mExtra;
644        }
645    }
646
647    // The View containing the zoom controls
648    private ExtendedZoomControls mZoomControls;
649    private Runnable mZoomControlRunnable;
650
651    private ZoomButtonsController mZoomButtonsController;
652
653    // These keep track of the center point of the zoom.  They are used to
654    // determine the point around which we should zoom.
655    private float mZoomCenterX;
656    private float mZoomCenterY;
657
658    private ZoomButtonsController.OnZoomListener mZoomListener =
659            new ZoomButtonsController.OnZoomListener() {
660
661        public void onVisibilityChanged(boolean visible) {
662            if (visible) {
663                switchOutDrawHistory();
664                updateZoomButtonsEnabled();
665            }
666        }
667
668        public void onZoom(boolean zoomIn) {
669            if (zoomIn) {
670                zoomIn();
671            } else {
672                zoomOut();
673            }
674
675            updateZoomButtonsEnabled();
676        }
677    };
678
679    /**
680     * Construct a new WebView with a Context object.
681     * @param context A Context object used to access application assets.
682     */
683    public WebView(Context context) {
684        this(context, null);
685    }
686
687    /**
688     * Construct a new WebView with layout parameters.
689     * @param context A Context object used to access application assets.
690     * @param attrs An AttributeSet passed to our parent.
691     */
692    public WebView(Context context, AttributeSet attrs) {
693        this(context, attrs, com.android.internal.R.attr.webViewStyle);
694    }
695
696    /**
697     * Construct a new WebView with layout parameters and a default style.
698     * @param context A Context object used to access application assets.
699     * @param attrs An AttributeSet passed to our parent.
700     * @param defStyle The default style resource ID.
701     */
702    public WebView(Context context, AttributeSet attrs, int defStyle) {
703        this(context, attrs, defStyle, null);
704    }
705
706    /**
707     * Construct a new WebView with layout parameters, a default style and a set
708     * of custom Javscript interfaces to be added to the WebView at initialization
709     * time. This guraratees that these interfaces will be available when the JS
710     * context is initialized.
711     * @param context A Context object used to access application assets.
712     * @param attrs An AttributeSet passed to our parent.
713     * @param defStyle The default style resource ID.
714     * @param javascriptInterfaces is a Map of intareface names, as keys, and
715     * object implementing those interfaces, as values.
716     * @hide pending API council approval.
717     */
718    protected WebView(Context context, AttributeSet attrs, int defStyle,
719            Map<String, Object> javascriptInterfaces) {
720        super(context, attrs, defStyle);
721        init();
722
723        mCallbackProxy = new CallbackProxy(context, this);
724        mWebViewCore = new WebViewCore(context, this, mCallbackProxy, javascriptInterfaces);
725        mDatabase = WebViewDatabase.getInstance(context);
726        mScroller = new Scroller(context);
727
728        mViewManager = new ViewManager(this);
729
730        mZoomButtonsController = new ZoomButtonsController(this);
731        mZoomButtonsController.setOnZoomListener(mZoomListener);
732        // ZoomButtonsController positions the buttons at the bottom, but in
733        // the middle.  Change their layout parameters so they appear on the
734        // right.
735        View controls = mZoomButtonsController.getZoomControls();
736        ViewGroup.LayoutParams params = controls.getLayoutParams();
737        if (params instanceof FrameLayout.LayoutParams) {
738            FrameLayout.LayoutParams frameParams = (FrameLayout.LayoutParams)
739                    params;
740            frameParams.gravity = Gravity.RIGHT;
741        }
742    }
743
744    private void updateZoomButtonsEnabled() {
745        boolean canZoomIn = mActualScale < mMaxZoomScale;
746        boolean canZoomOut = mActualScale > mMinZoomScale && !mInZoomOverview;
747        if (!canZoomIn && !canZoomOut) {
748            // Hide the zoom in and out buttons, as well as the fit to page
749            // button, if the page cannot zoom
750            mZoomButtonsController.getZoomControls().setVisibility(View.GONE);
751        } else {
752            // Bring back the hidden zoom controls.
753            mZoomButtonsController.getZoomControls()
754                    .setVisibility(View.VISIBLE);
755            // Set each one individually, as a page may be able to zoom in
756            // or out.
757            mZoomButtonsController.setZoomInEnabled(canZoomIn);
758            mZoomButtonsController.setZoomOutEnabled(canZoomOut);
759        }
760    }
761
762    private void init() {
763        setWillNotDraw(false);
764        setFocusable(true);
765        setFocusableInTouchMode(true);
766        setClickable(true);
767        setLongClickable(true);
768
769        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
770        int slop = configuration.getScaledTouchSlop();
771        mTouchSlopSquare = slop * slop;
772        mMinLockSnapReverseDistance = slop;
773        slop = configuration.getScaledDoubleTapSlop();
774        mDoubleTapSlopSquare = slop * slop;
775        final float density = getContext().getResources().getDisplayMetrics().density;
776        // use one line height, 16 based on our current default font, for how
777        // far we allow a touch be away from the edge of a link
778        mNavSlop = (int) (16 * density);
779        // density adjusted scale factors
780        DEFAULT_SCALE_PERCENT = (int) (100 * density);
781        mDefaultScale = density;
782        mActualScale = density;
783        mInvActualScale = 1 / density;
784        DEFAULT_MAX_ZOOM_SCALE = 4.0f * density;
785        DEFAULT_MIN_ZOOM_SCALE = 0.25f * density;
786        mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE;
787        mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE;
788        mMaximumFling = configuration.getScaledMaximumFlingVelocity();
789    }
790
791    /* package */void updateDefaultZoomDensity(int zoomDensity) {
792        final float density = getContext().getResources().getDisplayMetrics().density
793                * 100 / zoomDensity;
794        if (Math.abs(density - mDefaultScale) > 0.01) {
795            float scaleFactor = density / mDefaultScale;
796            // adjust the limits
797            mNavSlop = (int) (16 * density);
798            DEFAULT_SCALE_PERCENT = (int) (100 * density);
799            DEFAULT_MAX_ZOOM_SCALE = 4.0f * density;
800            DEFAULT_MIN_ZOOM_SCALE = 0.25f * density;
801            mDefaultScale = density;
802            mMaxZoomScale *= scaleFactor;
803            mMinZoomScale *= scaleFactor;
804            setNewZoomScale(mActualScale * scaleFactor, false);
805        }
806    }
807
808    /* package */ boolean onSavePassword(String schemePlusHost, String username,
809            String password, final Message resumeMsg) {
810       boolean rVal = false;
811       if (resumeMsg == null) {
812           // null resumeMsg implies saving password silently
813           mDatabase.setUsernamePassword(schemePlusHost, username, password);
814       } else {
815            final Message remember = mPrivateHandler.obtainMessage(
816                    REMEMBER_PASSWORD);
817            remember.getData().putString("host", schemePlusHost);
818            remember.getData().putString("username", username);
819            remember.getData().putString("password", password);
820            remember.obj = resumeMsg;
821
822            final Message neverRemember = mPrivateHandler.obtainMessage(
823                    NEVER_REMEMBER_PASSWORD);
824            neverRemember.getData().putString("host", schemePlusHost);
825            neverRemember.getData().putString("username", username);
826            neverRemember.getData().putString("password", password);
827            neverRemember.obj = resumeMsg;
828
829            new AlertDialog.Builder(getContext())
830                    .setTitle(com.android.internal.R.string.save_password_label)
831                    .setMessage(com.android.internal.R.string.save_password_message)
832                    .setPositiveButton(com.android.internal.R.string.save_password_notnow,
833                    new DialogInterface.OnClickListener() {
834                        public void onClick(DialogInterface dialog, int which) {
835                            resumeMsg.sendToTarget();
836                        }
837                    })
838                    .setNeutralButton(com.android.internal.R.string.save_password_remember,
839                    new DialogInterface.OnClickListener() {
840                        public void onClick(DialogInterface dialog, int which) {
841                            remember.sendToTarget();
842                        }
843                    })
844                    .setNegativeButton(com.android.internal.R.string.save_password_never,
845                    new DialogInterface.OnClickListener() {
846                        public void onClick(DialogInterface dialog, int which) {
847                            neverRemember.sendToTarget();
848                        }
849                    })
850                    .setOnCancelListener(new OnCancelListener() {
851                        public void onCancel(DialogInterface dialog) {
852                            resumeMsg.sendToTarget();
853                        }
854                    }).show();
855            // Return true so that WebViewCore will pause while the dialog is
856            // up.
857            rVal = true;
858        }
859       return rVal;
860    }
861
862    @Override
863    public void setScrollBarStyle(int style) {
864        if (style == View.SCROLLBARS_INSIDE_INSET
865                || style == View.SCROLLBARS_OUTSIDE_INSET) {
866            mOverlayHorizontalScrollbar = mOverlayVerticalScrollbar = false;
867        } else {
868            mOverlayHorizontalScrollbar = mOverlayVerticalScrollbar = true;
869        }
870        super.setScrollBarStyle(style);
871    }
872
873    /**
874     * Specify whether the horizontal scrollbar has overlay style.
875     * @param overlay TRUE if horizontal scrollbar should have overlay style.
876     */
877    public void setHorizontalScrollbarOverlay(boolean overlay) {
878        mOverlayHorizontalScrollbar = overlay;
879    }
880
881    /**
882     * Specify whether the vertical scrollbar has overlay style.
883     * @param overlay TRUE if vertical scrollbar should have overlay style.
884     */
885    public void setVerticalScrollbarOverlay(boolean overlay) {
886        mOverlayVerticalScrollbar = overlay;
887    }
888
889    /**
890     * Return whether horizontal scrollbar has overlay style
891     * @return TRUE if horizontal scrollbar has overlay style.
892     */
893    public boolean overlayHorizontalScrollbar() {
894        return mOverlayHorizontalScrollbar;
895    }
896
897    /**
898     * Return whether vertical scrollbar has overlay style
899     * @return TRUE if vertical scrollbar has overlay style.
900     */
901    public boolean overlayVerticalScrollbar() {
902        return mOverlayVerticalScrollbar;
903    }
904
905    /*
906     * Return the width of the view where the content of WebView should render
907     * to.
908     * Note: this can be called from WebCoreThread.
909     */
910    /* package */ int getViewWidth() {
911        if (!isVerticalScrollBarEnabled() || mOverlayVerticalScrollbar) {
912            return getWidth();
913        } else {
914            return getWidth() - getVerticalScrollbarWidth();
915        }
916    }
917
918    /*
919     * returns the height of the titlebarview (if any). Does not care about
920     * scrolling
921     */
922    private int getTitleHeight() {
923        return mTitleBar != null ? mTitleBar.getHeight() : 0;
924    }
925
926    /*
927     * Return the amount of the titlebarview (if any) that is visible
928     */
929    private int getVisibleTitleHeight() {
930        return Math.max(getTitleHeight() - mScrollY, 0);
931    }
932
933    /*
934     * Return the height of the view where the content of WebView should render
935     * to.  Note that this excludes mTitleBar, if there is one.
936     * Note: this can be called from WebCoreThread.
937     */
938    /* package */ int getViewHeight() {
939        int height = getHeight();
940        if (isHorizontalScrollBarEnabled() && !mOverlayHorizontalScrollbar) {
941            height -= getHorizontalScrollbarHeight();
942        }
943        return height - getVisibleTitleHeight();
944    }
945
946    /**
947     * @return The SSL certificate for the main top-level page or null if
948     * there is no certificate (the site is not secure).
949     */
950    public SslCertificate getCertificate() {
951        return mCertificate;
952    }
953
954    /**
955     * Sets the SSL certificate for the main top-level page.
956     */
957    public void setCertificate(SslCertificate certificate) {
958        // here, the certificate can be null (if the site is not secure)
959        mCertificate = certificate;
960    }
961
962    //-------------------------------------------------------------------------
963    // Methods called by activity
964    //-------------------------------------------------------------------------
965
966    /**
967     * Save the username and password for a particular host in the WebView's
968     * internal database.
969     * @param host The host that required the credentials.
970     * @param username The username for the given host.
971     * @param password The password for the given host.
972     */
973    public void savePassword(String host, String username, String password) {
974        mDatabase.setUsernamePassword(host, username, password);
975    }
976
977    /**
978     * Set the HTTP authentication credentials for a given host and realm.
979     *
980     * @param host The host for the credentials.
981     * @param realm The realm for the credentials.
982     * @param username The username for the password. If it is null, it means
983     *                 password can't be saved.
984     * @param password The password
985     */
986    public void setHttpAuthUsernamePassword(String host, String realm,
987            String username, String password) {
988        mDatabase.setHttpAuthUsernamePassword(host, realm, username, password);
989    }
990
991    /**
992     * Retrieve the HTTP authentication username and password for a given
993     * host & realm pair
994     *
995     * @param host The host for which the credentials apply.
996     * @param realm The realm for which the credentials apply.
997     * @return String[] if found, String[0] is username, which can be null and
998     *         String[1] is password. Return null if it can't find anything.
999     */
1000    public String[] getHttpAuthUsernamePassword(String host, String realm) {
1001        return mDatabase.getHttpAuthUsernamePassword(host, realm);
1002    }
1003
1004    /**
1005     * Destroy the internal state of the WebView. This method should be called
1006     * after the WebView has been removed from the view system. No other
1007     * methods may be called on a WebView after destroy.
1008     */
1009    public void destroy() {
1010        clearTextEntry();
1011        if (mWebViewCore != null) {
1012            // Set the handlers to null before destroying WebViewCore so no
1013            // more messages will be posted.
1014            mCallbackProxy.setWebViewClient(null);
1015            mCallbackProxy.setWebChromeClient(null);
1016            // Tell WebViewCore to destroy itself
1017            WebViewCore webViewCore = mWebViewCore;
1018            mWebViewCore = null; // prevent using partial webViewCore
1019            webViewCore.destroy();
1020            // Remove any pending messages that might not be serviced yet.
1021            mPrivateHandler.removeCallbacksAndMessages(null);
1022            mCallbackProxy.removeCallbacksAndMessages(null);
1023            // Wake up the WebCore thread just in case it is waiting for a
1024            // javascript dialog.
1025            synchronized (mCallbackProxy) {
1026                mCallbackProxy.notify();
1027            }
1028        }
1029        if (mNativeClass != 0) {
1030            nativeDestroy();
1031            mNativeClass = 0;
1032        }
1033    }
1034
1035    /**
1036     * Enables platform notifications of data state and proxy changes.
1037     */
1038    public static void enablePlatformNotifications() {
1039        Network.enablePlatformNotifications();
1040    }
1041
1042    /**
1043     * If platform notifications are enabled, this should be called
1044     * from the Activity's onPause() or onStop().
1045     */
1046    public static void disablePlatformNotifications() {
1047        Network.disablePlatformNotifications();
1048    }
1049
1050    /**
1051     * Sets JavaScript engine flags.
1052     *
1053     * @param flags JS engine flags in a String
1054     *
1055     * @hide pending API solidification
1056     */
1057    public void setJsFlags(String flags) {
1058        mWebViewCore.sendMessage(EventHub.SET_JS_FLAGS, flags);
1059    }
1060
1061    /**
1062     * Inform WebView of the network state. This is used to set
1063     * the javascript property window.navigator.isOnline and
1064     * generates the online/offline event as specified in HTML5, sec. 5.7.7
1065     * @param networkUp boolean indicating if network is available
1066     */
1067    public void setNetworkAvailable(boolean networkUp) {
1068        mWebViewCore.sendMessage(EventHub.SET_NETWORK_STATE,
1069                networkUp ? 1 : 0, 0);
1070    }
1071
1072    /**
1073     * Save the state of this WebView used in
1074     * {@link android.app.Activity#onSaveInstanceState}. Please note that this
1075     * method no longer stores the display data for this WebView. The previous
1076     * behavior could potentially leak files if {@link #restoreState} was never
1077     * called. See {@link #savePicture} and {@link #restorePicture} for saving
1078     * and restoring the display data.
1079     * @param outState The Bundle to store the WebView state.
1080     * @return The same copy of the back/forward list used to save the state. If
1081     *         saveState fails, the returned list will be null.
1082     * @see #savePicture
1083     * @see #restorePicture
1084     */
1085    public WebBackForwardList saveState(Bundle outState) {
1086        if (outState == null) {
1087            return null;
1088        }
1089        // We grab a copy of the back/forward list because a client of WebView
1090        // may have invalidated the history list by calling clearHistory.
1091        WebBackForwardList list = copyBackForwardList();
1092        final int currentIndex = list.getCurrentIndex();
1093        final int size = list.getSize();
1094        // We should fail saving the state if the list is empty or the index is
1095        // not in a valid range.
1096        if (currentIndex < 0 || currentIndex >= size || size == 0) {
1097            return null;
1098        }
1099        outState.putInt("index", currentIndex);
1100        // FIXME: This should just be a byte[][] instead of ArrayList but
1101        // Parcel.java does not have the code to handle multi-dimensional
1102        // arrays.
1103        ArrayList<byte[]> history = new ArrayList<byte[]>(size);
1104        for (int i = 0; i < size; i++) {
1105            WebHistoryItem item = list.getItemAtIndex(i);
1106            byte[] data = item.getFlattenedData();
1107            if (data == null) {
1108                // It would be very odd to not have any data for a given history
1109                // item. And we will fail to rebuild the history list without
1110                // flattened data.
1111                return null;
1112            }
1113            history.add(data);
1114        }
1115        outState.putSerializable("history", history);
1116        if (mCertificate != null) {
1117            outState.putBundle("certificate",
1118                               SslCertificate.saveState(mCertificate));
1119        }
1120        return list;
1121    }
1122
1123    /**
1124     * Save the current display data to the Bundle given. Used in conjunction
1125     * with {@link #saveState}.
1126     * @param b A Bundle to store the display data.
1127     * @param dest The file to store the serialized picture data. Will be
1128     *             overwritten with this WebView's picture data.
1129     * @return True if the picture was successfully saved.
1130     */
1131    public boolean savePicture(Bundle b, File dest) {
1132        if (dest == null || b == null) {
1133            return false;
1134        }
1135        final Picture p = capturePicture();
1136        try {
1137            final FileOutputStream out = new FileOutputStream(dest);
1138            p.writeToStream(out);
1139            out.close();
1140        } catch (FileNotFoundException e){
1141            e.printStackTrace();
1142        } catch (IOException e) {
1143            e.printStackTrace();
1144        } catch (RuntimeException e) {
1145            e.printStackTrace();
1146        }
1147        if (dest.length() > 0) {
1148            b.putInt("scrollX", mScrollX);
1149            b.putInt("scrollY", mScrollY);
1150            b.putFloat("scale", mActualScale);
1151            if (mInZoomOverview) {
1152                b.putFloat("lastScale", mLastScale);
1153            }
1154            return true;
1155        }
1156        return false;
1157    }
1158
1159    /**
1160     * Restore the display data that was save in {@link #savePicture}. Used in
1161     * conjunction with {@link #restoreState}.
1162     * @param b A Bundle containing the saved display data.
1163     * @param src The file where the picture data was stored.
1164     * @return True if the picture was successfully restored.
1165     */
1166    public boolean restorePicture(Bundle b, File src) {
1167        if (src == null || b == null) {
1168            return false;
1169        }
1170        if (src.exists()) {
1171            Picture p = null;
1172            try {
1173                final FileInputStream in = new FileInputStream(src);
1174                p = Picture.createFromStream(in);
1175                in.close();
1176            } catch (FileNotFoundException e){
1177                e.printStackTrace();
1178            } catch (RuntimeException e) {
1179                e.printStackTrace();
1180            } catch (IOException e) {
1181                e.printStackTrace();
1182            }
1183            if (p != null) {
1184                int sx = b.getInt("scrollX", 0);
1185                int sy = b.getInt("scrollY", 0);
1186                float scale = b.getFloat("scale", 1.0f);
1187                mDrawHistory = true;
1188                mHistoryPicture = p;
1189                mScrollX = sx;
1190                mScrollY = sy;
1191                mHistoryWidth = Math.round(p.getWidth() * scale);
1192                mHistoryHeight = Math.round(p.getHeight() * scale);
1193                // as getWidth() / getHeight() of the view are not
1194                // available yet, set up mActualScale, so that when
1195                // onSizeChanged() is called, the rest will be set
1196                // correctly
1197                mActualScale = scale;
1198                float lastScale = b.getFloat("lastScale", -1.0f);
1199                if (lastScale > 0) {
1200                    mInZoomOverview = true;
1201                    mLastScale = lastScale;
1202                } else {
1203                    mInZoomOverview = false;
1204                }
1205                invalidate();
1206                return true;
1207            }
1208        }
1209        return false;
1210    }
1211
1212    /**
1213     * Restore the state of this WebView from the given map used in
1214     * {@link android.app.Activity#onRestoreInstanceState}. This method should
1215     * be called to restore the state of the WebView before using the object. If
1216     * it is called after the WebView has had a chance to build state (load
1217     * pages, create a back/forward list, etc.) there may be undesirable
1218     * side-effects. Please note that this method no longer restores the
1219     * display data for this WebView. See {@link #savePicture} and {@link
1220     * #restorePicture} for saving and restoring the display data.
1221     * @param inState The incoming Bundle of state.
1222     * @return The restored back/forward list or null if restoreState failed.
1223     * @see #savePicture
1224     * @see #restorePicture
1225     */
1226    public WebBackForwardList restoreState(Bundle inState) {
1227        WebBackForwardList returnList = null;
1228        if (inState == null) {
1229            return returnList;
1230        }
1231        if (inState.containsKey("index") && inState.containsKey("history")) {
1232            mCertificate = SslCertificate.restoreState(
1233                inState.getBundle("certificate"));
1234
1235            final WebBackForwardList list = mCallbackProxy.getBackForwardList();
1236            final int index = inState.getInt("index");
1237            // We can't use a clone of the list because we need to modify the
1238            // shared copy, so synchronize instead to prevent concurrent
1239            // modifications.
1240            synchronized (list) {
1241                final List<byte[]> history =
1242                        (List<byte[]>) inState.getSerializable("history");
1243                final int size = history.size();
1244                // Check the index bounds so we don't crash in native code while
1245                // restoring the history index.
1246                if (index < 0 || index >= size) {
1247                    return null;
1248                }
1249                for (int i = 0; i < size; i++) {
1250                    byte[] data = history.remove(0);
1251                    if (data == null) {
1252                        // If we somehow have null data, we cannot reconstruct
1253                        // the item and thus our history list cannot be rebuilt.
1254                        return null;
1255                    }
1256                    WebHistoryItem item = new WebHistoryItem(data);
1257                    list.addHistoryItem(item);
1258                }
1259                // Grab the most recent copy to return to the caller.
1260                returnList = copyBackForwardList();
1261                // Update the copy to have the correct index.
1262                returnList.setCurrentIndex(index);
1263            }
1264            // Remove all pending messages because we are restoring previous
1265            // state.
1266            mWebViewCore.removeMessages();
1267            // Send a restore state message.
1268            mWebViewCore.sendMessage(EventHub.RESTORE_STATE, index);
1269        }
1270        return returnList;
1271    }
1272
1273    /**
1274     * Load the given url.
1275     * @param url The url of the resource to load.
1276     */
1277    public void loadUrl(String url) {
1278        if (url == null) {
1279            return;
1280        }
1281        switchOutDrawHistory();
1282        mWebViewCore.sendMessage(EventHub.LOAD_URL, url);
1283        clearTextEntry();
1284    }
1285
1286    /**
1287     * Load the url with postData using "POST" method into the WebView. If url
1288     * is not a network url, it will be loaded with {link
1289     * {@link #loadUrl(String)} instead.
1290     *
1291     * @param url The url of the resource to load.
1292     * @param postData The data will be passed to "POST" request.
1293     */
1294    public void postUrl(String url, byte[] postData) {
1295        if (URLUtil.isNetworkUrl(url)) {
1296            switchOutDrawHistory();
1297            WebViewCore.PostUrlData arg = new WebViewCore.PostUrlData();
1298            arg.mUrl = url;
1299            arg.mPostData = postData;
1300            mWebViewCore.sendMessage(EventHub.POST_URL, arg);
1301            clearTextEntry();
1302        } else {
1303            loadUrl(url);
1304        }
1305    }
1306
1307    /**
1308     * Load the given data into the WebView. This will load the data into
1309     * WebView using the data: scheme. Content loaded through this mechanism
1310     * does not have the ability to load content from the network.
1311     * @param data A String of data in the given encoding.
1312     * @param mimeType The MIMEType of the data. i.e. text/html, image/jpeg
1313     * @param encoding The encoding of the data. i.e. utf-8, base64
1314     */
1315    public void loadData(String data, String mimeType, String encoding) {
1316        loadUrl("data:" + mimeType + ";" + encoding + "," + data);
1317    }
1318
1319    /**
1320     * Load the given data into the WebView, use the provided URL as the base
1321     * URL for the content. The base URL is the URL that represents the page
1322     * that is loaded through this interface. As such, it is used for the
1323     * history entry and to resolve any relative URLs. The failUrl is used if
1324     * browser fails to load the data provided. If it is empty or null, and the
1325     * load fails, then no history entry is created.
1326     * <p>
1327     * Note for post 1.0. Due to the change in the WebKit, the access to asset
1328     * files through "file:///android_asset/" for the sub resources is more
1329     * restricted. If you provide null or empty string as baseUrl, you won't be
1330     * able to access asset files. If the baseUrl is anything other than
1331     * http(s)/ftp(s)/about/javascript as scheme, you can access asset files for
1332     * sub resources.
1333     *
1334     * @param baseUrl Url to resolve relative paths with, if null defaults to
1335     *            "about:blank"
1336     * @param data A String of data in the given encoding.
1337     * @param mimeType The MIMEType of the data. i.e. text/html. If null,
1338     *            defaults to "text/html"
1339     * @param encoding The encoding of the data. i.e. utf-8, us-ascii
1340     * @param failUrl URL to use if the content fails to load or null.
1341     */
1342    public void loadDataWithBaseURL(String baseUrl, String data,
1343            String mimeType, String encoding, String failUrl) {
1344
1345        if (baseUrl != null && baseUrl.toLowerCase().startsWith("data:")) {
1346            loadData(data, mimeType, encoding);
1347            return;
1348        }
1349        switchOutDrawHistory();
1350        WebViewCore.BaseUrlData arg = new WebViewCore.BaseUrlData();
1351        arg.mBaseUrl = baseUrl;
1352        arg.mData = data;
1353        arg.mMimeType = mimeType;
1354        arg.mEncoding = encoding;
1355        arg.mFailUrl = failUrl;
1356        mWebViewCore.sendMessage(EventHub.LOAD_DATA, arg);
1357        clearTextEntry();
1358    }
1359
1360    /**
1361     * Stop the current load.
1362     */
1363    public void stopLoading() {
1364        // TODO: should we clear all the messages in the queue before sending
1365        // STOP_LOADING?
1366        switchOutDrawHistory();
1367        mWebViewCore.sendMessage(EventHub.STOP_LOADING);
1368    }
1369
1370    /**
1371     * Reload the current url.
1372     */
1373    public void reload() {
1374        clearTextEntry();
1375        switchOutDrawHistory();
1376        mWebViewCore.sendMessage(EventHub.RELOAD);
1377    }
1378
1379    /**
1380     * Return true if this WebView has a back history item.
1381     * @return True iff this WebView has a back history item.
1382     */
1383    public boolean canGoBack() {
1384        WebBackForwardList l = mCallbackProxy.getBackForwardList();
1385        synchronized (l) {
1386            if (l.getClearPending()) {
1387                return false;
1388            } else {
1389                return l.getCurrentIndex() > 0;
1390            }
1391        }
1392    }
1393
1394    /**
1395     * Go back in the history of this WebView.
1396     */
1397    public void goBack() {
1398        goBackOrForward(-1);
1399    }
1400
1401    /**
1402     * Return true if this WebView has a forward history item.
1403     * @return True iff this Webview has a forward history item.
1404     */
1405    public boolean canGoForward() {
1406        WebBackForwardList l = mCallbackProxy.getBackForwardList();
1407        synchronized (l) {
1408            if (l.getClearPending()) {
1409                return false;
1410            } else {
1411                return l.getCurrentIndex() < l.getSize() - 1;
1412            }
1413        }
1414    }
1415
1416    /**
1417     * Go forward in the history of this WebView.
1418     */
1419    public void goForward() {
1420        goBackOrForward(1);
1421    }
1422
1423    /**
1424     * Return true if the page can go back or forward the given
1425     * number of steps.
1426     * @param steps The negative or positive number of steps to move the
1427     *              history.
1428     */
1429    public boolean canGoBackOrForward(int steps) {
1430        WebBackForwardList l = mCallbackProxy.getBackForwardList();
1431        synchronized (l) {
1432            if (l.getClearPending()) {
1433                return false;
1434            } else {
1435                int newIndex = l.getCurrentIndex() + steps;
1436                return newIndex >= 0 && newIndex < l.getSize();
1437            }
1438        }
1439    }
1440
1441    /**
1442     * Go to the history item that is the number of steps away from
1443     * the current item. Steps is negative if backward and positive
1444     * if forward.
1445     * @param steps The number of steps to take back or forward in the back
1446     *              forward list.
1447     */
1448    public void goBackOrForward(int steps) {
1449        goBackOrForward(steps, false);
1450    }
1451
1452    private void goBackOrForward(int steps, boolean ignoreSnapshot) {
1453        // every time we go back or forward, we want to reset the
1454        // WebView certificate:
1455        // if the new site is secure, we will reload it and get a
1456        // new certificate set;
1457        // if the new site is not secure, the certificate must be
1458        // null, and that will be the case
1459        mCertificate = null;
1460        if (steps != 0) {
1461            clearTextEntry();
1462            mWebViewCore.sendMessage(EventHub.GO_BACK_FORWARD, steps,
1463                    ignoreSnapshot ? 1 : 0);
1464        }
1465    }
1466
1467    private boolean extendScroll(int y) {
1468        int finalY = mScroller.getFinalY();
1469        int newY = pinLocY(finalY + y);
1470        if (newY == finalY) return false;
1471        mScroller.setFinalY(newY);
1472        mScroller.extendDuration(computeDuration(0, y));
1473        return true;
1474    }
1475
1476    /**
1477     * Scroll the contents of the view up by half the view size
1478     * @param top true to jump to the top of the page
1479     * @return true if the page was scrolled
1480     */
1481    public boolean pageUp(boolean top) {
1482        if (mNativeClass == 0) {
1483            return false;
1484        }
1485        nativeClearCursor(); // start next trackball movement from page edge
1486        if (top) {
1487            // go to the top of the document
1488            return pinScrollTo(mScrollX, 0, true, 0);
1489        }
1490        // Page up
1491        int h = getHeight();
1492        int y;
1493        if (h > 2 * PAGE_SCROLL_OVERLAP) {
1494            y = -h + PAGE_SCROLL_OVERLAP;
1495        } else {
1496            y = -h / 2;
1497        }
1498        mUserScroll = true;
1499        return mScroller.isFinished() ? pinScrollBy(0, y, true, 0)
1500                : extendScroll(y);
1501    }
1502
1503    /**
1504     * Scroll the contents of the view down by half the page size
1505     * @param bottom true to jump to bottom of page
1506     * @return true if the page was scrolled
1507     */
1508    public boolean pageDown(boolean bottom) {
1509        if (mNativeClass == 0) {
1510            return false;
1511        }
1512        nativeClearCursor(); // start next trackball movement from page edge
1513        if (bottom) {
1514            return pinScrollTo(mScrollX, mContentHeight, true, 0);
1515        }
1516        // Page down.
1517        int h = getHeight();
1518        int y;
1519        if (h > 2 * PAGE_SCROLL_OVERLAP) {
1520            y = h - PAGE_SCROLL_OVERLAP;
1521        } else {
1522            y = h / 2;
1523        }
1524        mUserScroll = true;
1525        return mScroller.isFinished() ? pinScrollBy(0, y, true, 0)
1526                : extendScroll(y);
1527    }
1528
1529    /**
1530     * Clear the view so that onDraw() will draw nothing but white background,
1531     * and onMeasure() will return 0 if MeasureSpec is not MeasureSpec.EXACTLY
1532     */
1533    public void clearView() {
1534        mContentWidth = 0;
1535        mContentHeight = 0;
1536        mWebViewCore.sendMessage(EventHub.CLEAR_CONTENT);
1537    }
1538
1539    /**
1540     * Return a new picture that captures the current display of the webview.
1541     * This is a copy of the display, and will be unaffected if the webview
1542     * later loads a different URL.
1543     *
1544     * @return a picture containing the current contents of the view. Note this
1545     *         picture is of the entire document, and is not restricted to the
1546     *         bounds of the view.
1547     */
1548    public Picture capturePicture() {
1549        if (null == mWebViewCore) return null; // check for out of memory tab
1550        return mWebViewCore.copyContentPicture();
1551    }
1552
1553    /**
1554     *  Return true if the browser is displaying a TextView for text input.
1555     */
1556    private boolean inEditingMode() {
1557        return mWebTextView != null && mWebTextView.getParent() != null
1558                && mWebTextView.hasFocus();
1559    }
1560
1561    private void clearTextEntry() {
1562        if (inEditingMode()) {
1563            mWebTextView.remove();
1564        }
1565    }
1566
1567    /**
1568     * Return the current scale of the WebView
1569     * @return The current scale.
1570     */
1571    public float getScale() {
1572        return mActualScale;
1573    }
1574
1575    /**
1576     * Set the initial scale for the WebView. 0 means default. If
1577     * {@link WebSettings#getUseWideViewPort()} is true, it zooms out all the
1578     * way. Otherwise it starts with 100%. If initial scale is greater than 0,
1579     * WebView starts will this value as initial scale.
1580     *
1581     * @param scaleInPercent The initial scale in percent.
1582     */
1583    public void setInitialScale(int scaleInPercent) {
1584        mInitialScale = scaleInPercent;
1585    }
1586
1587    /**
1588     * Invoke the graphical zoom picker widget for this WebView. This will
1589     * result in the zoom widget appearing on the screen to control the zoom
1590     * level of this WebView.
1591     */
1592    public void invokeZoomPicker() {
1593        if (!getSettings().supportZoom()) {
1594            Log.w(LOGTAG, "This WebView doesn't support zoom.");
1595            return;
1596        }
1597        clearTextEntry();
1598        if (getSettings().getBuiltInZoomControls()) {
1599            mZoomButtonsController.setVisible(true);
1600        } else {
1601            mPrivateHandler.removeCallbacks(mZoomControlRunnable);
1602            mPrivateHandler.postDelayed(mZoomControlRunnable,
1603                    ZOOM_CONTROLS_TIMEOUT);
1604        }
1605    }
1606
1607    /**
1608     * Return a HitTestResult based on the current cursor node. If a HTML::a tag
1609     * is found and the anchor has a non-javascript url, the HitTestResult type
1610     * is set to SRC_ANCHOR_TYPE and the url is set in the "extra" field. If the
1611     * anchor does not have a url or if it is a javascript url, the type will
1612     * be UNKNOWN_TYPE and the url has to be retrieved through
1613     * {@link #requestFocusNodeHref} asynchronously. If a HTML::img tag is
1614     * found, the HitTestResult type is set to IMAGE_TYPE and the url is set in
1615     * the "extra" field. A type of
1616     * SRC_IMAGE_ANCHOR_TYPE indicates an anchor with a url that has an image as
1617     * a child node. If a phone number is found, the HitTestResult type is set
1618     * to PHONE_TYPE and the phone number is set in the "extra" field of
1619     * HitTestResult. If a map address is found, the HitTestResult type is set
1620     * to GEO_TYPE and the address is set in the "extra" field of HitTestResult.
1621     * If an email address is found, the HitTestResult type is set to EMAIL_TYPE
1622     * and the email is set in the "extra" field of HitTestResult. Otherwise,
1623     * HitTestResult type is set to UNKNOWN_TYPE.
1624     */
1625    public HitTestResult getHitTestResult() {
1626        if (mNativeClass == 0) {
1627            return null;
1628        }
1629
1630        HitTestResult result = new HitTestResult();
1631        if (nativeHasCursorNode()) {
1632            if (nativeCursorIsTextInput()) {
1633                result.setType(HitTestResult.EDIT_TEXT_TYPE);
1634            } else {
1635                String text = nativeCursorText();
1636                if (text != null) {
1637                    if (text.startsWith(SCHEME_TEL)) {
1638                        result.setType(HitTestResult.PHONE_TYPE);
1639                        result.setExtra(text.substring(SCHEME_TEL.length()));
1640                    } else if (text.startsWith(SCHEME_MAILTO)) {
1641                        result.setType(HitTestResult.EMAIL_TYPE);
1642                        result.setExtra(text.substring(SCHEME_MAILTO.length()));
1643                    } else if (text.startsWith(SCHEME_GEO)) {
1644                        result.setType(HitTestResult.GEO_TYPE);
1645                        result.setExtra(URLDecoder.decode(text
1646                                .substring(SCHEME_GEO.length())));
1647                    } else if (nativeCursorIsAnchor()) {
1648                        result.setType(HitTestResult.SRC_ANCHOR_TYPE);
1649                        result.setExtra(text);
1650                    }
1651                }
1652            }
1653        }
1654        int type = result.getType();
1655        if (type == HitTestResult.UNKNOWN_TYPE
1656                || type == HitTestResult.SRC_ANCHOR_TYPE) {
1657            // Now check to see if it is an image.
1658            int contentX = viewToContentX((int) mLastTouchX + mScrollX);
1659            int contentY = viewToContentY((int) mLastTouchY + mScrollY);
1660            String text = nativeImageURI(contentX, contentY);
1661            if (text != null) {
1662                result.setType(type == HitTestResult.UNKNOWN_TYPE ?
1663                        HitTestResult.IMAGE_TYPE :
1664                        HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
1665                result.setExtra(text);
1666            }
1667        }
1668        return result;
1669    }
1670
1671    /**
1672     * Request the href of an anchor element due to getFocusNodePath returning
1673     * "href." If hrefMsg is null, this method returns immediately and does not
1674     * dispatch hrefMsg to its target.
1675     *
1676     * @param hrefMsg This message will be dispatched with the result of the
1677     *            request as the data member with "url" as key. The result can
1678     *            be null.
1679     */
1680    // FIXME: API change required to change the name of this function.  We now
1681    // look at the cursor node, and not the focus node.  Also, what is
1682    // getFocusNodePath?
1683    public void requestFocusNodeHref(Message hrefMsg) {
1684        if (hrefMsg == null || mNativeClass == 0) {
1685            return;
1686        }
1687        if (nativeCursorIsAnchor()) {
1688            mWebViewCore.sendMessage(EventHub.REQUEST_CURSOR_HREF,
1689                    nativeCursorFramePointer(), nativeCursorNodePointer(),
1690                    hrefMsg);
1691        }
1692    }
1693
1694    /**
1695     * Request the url of the image last touched by the user. msg will be sent
1696     * to its target with a String representing the url as its object.
1697     *
1698     * @param msg This message will be dispatched with the result of the request
1699     *            as the data member with "url" as key. The result can be null.
1700     */
1701    public void requestImageRef(Message msg) {
1702        int contentX = viewToContentX((int) mLastTouchX + mScrollX);
1703        int contentY = viewToContentY((int) mLastTouchY + mScrollY);
1704        String ref = nativeImageURI(contentX, contentY);
1705        Bundle data = msg.getData();
1706        data.putString("url", ref);
1707        msg.setData(data);
1708        msg.sendToTarget();
1709    }
1710
1711    private static int pinLoc(int x, int viewMax, int docMax) {
1712//        Log.d(LOGTAG, "-- pinLoc " + x + " " + viewMax + " " + docMax);
1713        if (docMax < viewMax) {   // the doc has room on the sides for "blank"
1714            // pin the short document to the top/left of the screen
1715            x = 0;
1716//            Log.d(LOGTAG, "--- center " + x);
1717        } else if (x < 0) {
1718            x = 0;
1719//            Log.d(LOGTAG, "--- zero");
1720        } else if (x + viewMax > docMax) {
1721            x = docMax - viewMax;
1722//            Log.d(LOGTAG, "--- pin " + x);
1723        }
1724        return x;
1725    }
1726
1727    // Expects x in view coordinates
1728    private int pinLocX(int x) {
1729        return pinLoc(x, getViewWidth(), computeHorizontalScrollRange());
1730    }
1731
1732    // Expects y in view coordinates
1733    private int pinLocY(int y) {
1734        int titleH = getTitleHeight();
1735        // if the titlebar is still visible, just pin against 0
1736        if (y <= titleH) {
1737            return Math.max(y, 0);
1738        }
1739        // convert to 0-based coordinate (subtract the title height)
1740        // pin(), and then add the title height back in
1741        return pinLoc(y - titleH, getViewHeight(),
1742                      computeVerticalScrollRange()) + titleH;
1743    }
1744
1745    /**
1746     * A title bar which is embedded in this WebView, and scrolls along with it
1747     * vertically, but not horizontally.
1748     */
1749    private View mTitleBar;
1750
1751    /**
1752     * Since we draw the title bar ourselves, we removed the shadow from the
1753     * browser's activity.  We do want a shadow at the bottom of the title bar,
1754     * or at the top of the screen if the title bar is not visible.  This
1755     * drawable serves that purpose.
1756     */
1757    private Drawable mTitleShadow;
1758
1759    /**
1760     * Add or remove a title bar to be embedded into the WebView, and scroll
1761     * along with it vertically, while remaining in view horizontally. Pass
1762     * null to remove the title bar from the WebView, and return to drawing
1763     * the WebView normally without translating to account for the title bar.
1764     * @hide
1765     */
1766    public void setEmbeddedTitleBar(View v) {
1767        if (mTitleBar == v) return;
1768        if (mTitleBar != null) {
1769            removeView(mTitleBar);
1770        }
1771        if (null != v) {
1772            addView(v, new AbsoluteLayout.LayoutParams(
1773                    ViewGroup.LayoutParams.FILL_PARENT,
1774                    ViewGroup.LayoutParams.WRAP_CONTENT, 0, 0));
1775            if (mTitleShadow == null) {
1776                mTitleShadow = (Drawable) mContext.getResources().getDrawable(
1777                        com.android.internal.R.drawable.title_bar_shadow);
1778            }
1779        }
1780        mTitleBar = v;
1781    }
1782
1783    /**
1784     * Given an x coordinate in view space, convert it to content space.  Also
1785     * may be used for absolute heights (such as for the WebTextView's
1786     * textSize, which is unaffected by the height of the title bar).
1787     */
1788    /*package*/ int viewToContentX(int x) {
1789        return Math.round(x * mInvActualScale);
1790    }
1791
1792    /**
1793     * Given a y coordinate in view space, convert it to content space.
1794     * Takes into account the height of the title bar if there is one
1795     * embedded into the WebView.
1796     */
1797    /*package*/ int viewToContentY(int y) {
1798        return viewToContentX(y - getTitleHeight());
1799    }
1800
1801    /**
1802     * Given a distance in content space, convert it to view space. Note: this
1803     * does not reflect translation, just scaling, so this should not be called
1804     * with coordinates, but should be called for dimensions like width or
1805     * height.
1806     */
1807    /*package*/ int contentToViewDimension(int d) {
1808        return Math.round(d * mActualScale);
1809    }
1810
1811    /**
1812     * Given an x coordinate in content space, convert it to view
1813     * space.  Also used for absolute heights.
1814     */
1815    /*package*/ int contentToViewX(int x) {
1816        return contentToViewDimension(x);
1817    }
1818
1819    /**
1820     * Given a y coordinate in content space, convert it to view
1821     * space.  Takes into account the height of the title bar.
1822     */
1823    /*package*/ int contentToViewY(int y) {
1824        return contentToViewDimension(y) + getTitleHeight();
1825    }
1826
1827    private Rect contentToViewRect(Rect x) {
1828        return new Rect(contentToViewX(x.left), contentToViewY(x.top),
1829                        contentToViewX(x.right), contentToViewY(x.bottom));
1830    }
1831
1832    /*  To invalidate a rectangle in content coordinates, we need to transform
1833        the rect into view coordinates, so we can then call invalidate(...).
1834
1835        Normally, we would just call contentToView[XY](...), which eventually
1836        calls Math.round(coordinate * mActualScale). However, for invalidates,
1837        we need to account for the slop that occurs with antialiasing. To
1838        address that, we are a little more liberal in the size of the rect that
1839        we invalidate.
1840
1841        This liberal calculation calls floor() for the top/left, and ceil() for
1842        the bottom/right coordinates. This catches the possible extra pixels of
1843        antialiasing that we might have missed with just round().
1844     */
1845
1846    // Called by JNI to invalidate the View, given rectangle coordinates in
1847    // content space
1848    private void viewInvalidate(int l, int t, int r, int b) {
1849        final float scale = mActualScale;
1850        final int dy = getTitleHeight();
1851        invalidate((int)Math.floor(l * scale),
1852                   (int)Math.floor(t * scale) + dy,
1853                   (int)Math.ceil(r * scale),
1854                   (int)Math.ceil(b * scale) + dy);
1855    }
1856
1857    // Called by JNI to invalidate the View after a delay, given rectangle
1858    // coordinates in content space
1859    private void viewInvalidateDelayed(long delay, int l, int t, int r, int b) {
1860        final float scale = mActualScale;
1861        final int dy = getTitleHeight();
1862        postInvalidateDelayed(delay,
1863                              (int)Math.floor(l * scale),
1864                              (int)Math.floor(t * scale) + dy,
1865                              (int)Math.ceil(r * scale),
1866                              (int)Math.ceil(b * scale) + dy);
1867    }
1868
1869    private void invalidateContentRect(Rect r) {
1870        viewInvalidate(r.left, r.top, r.right, r.bottom);
1871    }
1872
1873    // stop the scroll animation, and don't let a subsequent fling add
1874    // to the existing velocity
1875    private void abortAnimation() {
1876        mScroller.abortAnimation();
1877        mLastVelocity = 0;
1878    }
1879
1880    /* call from webcoreview.draw(), so we're still executing in the UI thread
1881    */
1882    private void recordNewContentSize(int w, int h, boolean updateLayout) {
1883
1884        // premature data from webkit, ignore
1885        if ((w | h) == 0) {
1886            return;
1887        }
1888
1889        // don't abort a scroll animation if we didn't change anything
1890        if (mContentWidth != w || mContentHeight != h) {
1891            // record new dimensions
1892            mContentWidth = w;
1893            mContentHeight = h;
1894            // If history Picture is drawn, don't update scroll. They will be
1895            // updated when we get out of that mode.
1896            if (!mDrawHistory) {
1897                // repin our scroll, taking into account the new content size
1898                int oldX = mScrollX;
1899                int oldY = mScrollY;
1900                mScrollX = pinLocX(mScrollX);
1901                mScrollY = pinLocY(mScrollY);
1902                // android.util.Log.d("skia", "recordNewContentSize -
1903                // abortAnimation");
1904                abortAnimation(); // just in case
1905                if (oldX != mScrollX || oldY != mScrollY) {
1906                    sendOurVisibleRect();
1907                }
1908            }
1909        }
1910        contentSizeChanged(updateLayout);
1911    }
1912
1913    private void setNewZoomScale(float scale, boolean force) {
1914        if (scale < mMinZoomScale) {
1915            scale = mMinZoomScale;
1916        } else if (scale > mMaxZoomScale) {
1917            scale = mMaxZoomScale;
1918        }
1919        if (scale != mActualScale || force) {
1920            if (mDrawHistory) {
1921                // If history Picture is drawn, don't update scroll. They will
1922                // be updated when we get out of that mode.
1923                if (scale != mActualScale && !mPreviewZoomOnly) {
1924                    mCallbackProxy.onScaleChanged(mActualScale, scale);
1925                }
1926                mActualScale = scale;
1927                mInvActualScale = 1 / scale;
1928                if (!mPreviewZoomOnly) {
1929                    sendViewSizeZoom();
1930                }
1931            } else {
1932                // update our scroll so we don't appear to jump
1933                // i.e. keep the center of the doc in the center of the view
1934
1935                int oldX = mScrollX;
1936                int oldY = mScrollY;
1937                float ratio = scale * mInvActualScale;   // old inverse
1938                float sx = ratio * oldX + (ratio - 1) * mZoomCenterX;
1939                float sy = ratio * oldY + (ratio - 1)
1940                        * (mZoomCenterY - getTitleHeight());
1941
1942                // now update our new scale and inverse
1943                if (scale != mActualScale && !mPreviewZoomOnly) {
1944                    mCallbackProxy.onScaleChanged(mActualScale, scale);
1945                }
1946                mActualScale = scale;
1947                mInvActualScale = 1 / scale;
1948
1949                // Scale all the child views
1950                mViewManager.scaleAll();
1951
1952                // as we don't have animation for scaling, don't do animation
1953                // for scrolling, as it causes weird intermediate state
1954                //        pinScrollTo(Math.round(sx), Math.round(sy));
1955                mScrollX = pinLocX(Math.round(sx));
1956                mScrollY = pinLocY(Math.round(sy));
1957
1958                if (!mPreviewZoomOnly) {
1959                    sendViewSizeZoom();
1960                    sendOurVisibleRect();
1961                }
1962            }
1963        }
1964    }
1965
1966    // Used to avoid sending many visible rect messages.
1967    private Rect mLastVisibleRectSent;
1968    private Rect mLastGlobalRect;
1969
1970    private Rect sendOurVisibleRect() {
1971        Rect rect = new Rect();
1972        calcOurContentVisibleRect(rect);
1973        // Rect.equals() checks for null input.
1974        if (!rect.equals(mLastVisibleRectSent)) {
1975            Point pos = new Point(rect.left, rect.top);
1976            mWebViewCore.sendMessage(EventHub.SET_SCROLL_OFFSET,
1977                    nativeMoveGeneration(), 0, pos);
1978            mLastVisibleRectSent = rect;
1979        }
1980        Rect globalRect = new Rect();
1981        if (getGlobalVisibleRect(globalRect)
1982                && !globalRect.equals(mLastGlobalRect)) {
1983            if (DebugFlags.WEB_VIEW) {
1984                Log.v(LOGTAG, "sendOurVisibleRect=(" + globalRect.left + ","
1985                        + globalRect.top + ",r=" + globalRect.right + ",b="
1986                        + globalRect.bottom);
1987            }
1988            // TODO: the global offset is only used by windowRect()
1989            // in ChromeClientAndroid ; other clients such as touch
1990            // and mouse events could return view + screen relative points.
1991            mWebViewCore.sendMessage(EventHub.SET_GLOBAL_BOUNDS, globalRect);
1992            mLastGlobalRect = globalRect;
1993        }
1994        return rect;
1995    }
1996
1997    // Sets r to be the visible rectangle of our webview in view coordinates
1998    private void calcOurVisibleRect(Rect r) {
1999        Point p = new Point();
2000        getGlobalVisibleRect(r, p);
2001        r.offset(-p.x, -p.y);
2002        if (mFindIsUp) {
2003            r.bottom -= FIND_HEIGHT;
2004        }
2005    }
2006
2007    // Sets r to be our visible rectangle in content coordinates
2008    private void calcOurContentVisibleRect(Rect r) {
2009        calcOurVisibleRect(r);
2010        r.left = viewToContentX(r.left);
2011        r.top = viewToContentY(r.top);
2012        r.right = viewToContentX(r.right);
2013        r.bottom = viewToContentY(r.bottom);
2014    }
2015
2016    static class ViewSizeData {
2017        int mWidth;
2018        int mHeight;
2019        int mTextWrapWidth;
2020        float mScale;
2021        boolean mIgnoreHeight;
2022    }
2023
2024    /**
2025     * Compute unzoomed width and height, and if they differ from the last
2026     * values we sent, send them to webkit (to be used has new viewport)
2027     *
2028     * @return true if new values were sent
2029     */
2030    private boolean sendViewSizeZoom() {
2031        int viewWidth = getViewWidth();
2032        int newWidth = Math.round(viewWidth * mInvActualScale);
2033        int newHeight = Math.round(getViewHeight() * mInvActualScale);
2034        /*
2035         * Because the native side may have already done a layout before the
2036         * View system was able to measure us, we have to send a height of 0 to
2037         * remove excess whitespace when we grow our width. This will trigger a
2038         * layout and a change in content size. This content size change will
2039         * mean that contentSizeChanged will either call this method directly or
2040         * indirectly from onSizeChanged.
2041         */
2042        if (newWidth > mLastWidthSent && mWrapContent) {
2043            newHeight = 0;
2044        }
2045        // Avoid sending another message if the dimensions have not changed.
2046        if (newWidth != mLastWidthSent || newHeight != mLastHeightSent) {
2047            ViewSizeData data = new ViewSizeData();
2048            data.mWidth = newWidth;
2049            data.mHeight = newHeight;
2050            // while in zoom overview mode, the text are wrapped to the screen
2051            // width matching mLastScale. So that we don't trigger re-flow while
2052            // toggling between overview mode and normal mode.
2053            data.mTextWrapWidth = mInZoomOverview ? Math.round(viewWidth
2054                    / mLastScale) : newWidth;
2055            data.mScale = mActualScale;
2056            data.mIgnoreHeight = mZoomScale != 0 && !mHeightCanMeasure;
2057            mWebViewCore.sendMessage(EventHub.VIEW_SIZE_CHANGED, data);
2058            mLastWidthSent = newWidth;
2059            mLastHeightSent = newHeight;
2060            return true;
2061        }
2062        return false;
2063    }
2064
2065    @Override
2066    protected int computeHorizontalScrollRange() {
2067        if (mDrawHistory) {
2068            return mHistoryWidth;
2069        } else {
2070            // to avoid rounding error caused unnecessary scrollbar, use floor
2071            return (int) Math.floor(mContentWidth * mActualScale);
2072        }
2073    }
2074
2075    // Make sure this stays in sync with the actual height of the FindDialog.
2076    private static final int FIND_HEIGHT = 79;
2077
2078    @Override
2079    protected int computeVerticalScrollRange() {
2080        if (mDrawHistory) {
2081            return mHistoryHeight;
2082        } else {
2083            // to avoid rounding error caused unnecessary scrollbar, use floor
2084            return (int) Math.floor(mContentHeight * mActualScale);
2085        }
2086    }
2087
2088    @Override
2089    protected int computeVerticalScrollOffset() {
2090        return Math.max(mScrollY - getTitleHeight(), 0);
2091    }
2092
2093    @Override
2094    protected int computeVerticalScrollExtent() {
2095        return getViewHeight();
2096    }
2097
2098    /** @hide */
2099    @Override
2100    protected void onDrawVerticalScrollBar(Canvas canvas,
2101                                           Drawable scrollBar,
2102                                           int l, int t, int r, int b) {
2103        scrollBar.setBounds(l, t + getVisibleTitleHeight(), r, b);
2104        scrollBar.draw(canvas);
2105    }
2106
2107    /**
2108     * Get the url for the current page. This is not always the same as the url
2109     * passed to WebViewClient.onPageStarted because although the load for
2110     * that url has begun, the current page may not have changed.
2111     * @return The url for the current page.
2112     */
2113    public String getUrl() {
2114        WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
2115        return h != null ? h.getUrl() : null;
2116    }
2117
2118    /**
2119     * Get the original url for the current page. This is not always the same
2120     * as the url passed to WebViewClient.onPageStarted because although the
2121     * load for that url has begun, the current page may not have changed.
2122     * Also, there may have been redirects resulting in a different url to that
2123     * originally requested.
2124     * @return The url that was originally requested for the current page.
2125     */
2126    public String getOriginalUrl() {
2127        WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
2128        return h != null ? h.getOriginalUrl() : null;
2129    }
2130
2131    /**
2132     * Get the title for the current page. This is the title of the current page
2133     * until WebViewClient.onReceivedTitle is called.
2134     * @return The title for the current page.
2135     */
2136    public String getTitle() {
2137        WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
2138        return h != null ? h.getTitle() : null;
2139    }
2140
2141    /**
2142     * Get the favicon for the current page. This is the favicon of the current
2143     * page until WebViewClient.onReceivedIcon is called.
2144     * @return The favicon for the current page.
2145     */
2146    public Bitmap getFavicon() {
2147        WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
2148        return h != null ? h.getFavicon() : null;
2149    }
2150
2151    /**
2152     * Get the touch icon url for the apple-touch-icon <link> element.
2153     * @hide
2154     */
2155    public String getTouchIconUrl() {
2156        WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
2157        return h != null ? h.getTouchIconUrl() : null;
2158    }
2159
2160    /**
2161     * Get the progress for the current page.
2162     * @return The progress for the current page between 0 and 100.
2163     */
2164    public int getProgress() {
2165        return mCallbackProxy.getProgress();
2166    }
2167
2168    /**
2169     * @return the height of the HTML content.
2170     */
2171    public int getContentHeight() {
2172        return mContentHeight;
2173    }
2174
2175    /**
2176     * Pause all layout, parsing, and javascript timers for all webviews. This
2177     * is a global requests, not restricted to just this webview. This can be
2178     * useful if the application has been paused.
2179     */
2180    public void pauseTimers() {
2181        mWebViewCore.sendMessage(EventHub.PAUSE_TIMERS);
2182    }
2183
2184    /**
2185     * Resume all layout, parsing, and javascript timers for all webviews.
2186     * This will resume dispatching all timers.
2187     */
2188    public void resumeTimers() {
2189        mWebViewCore.sendMessage(EventHub.RESUME_TIMERS);
2190    }
2191
2192    /**
2193     * Call this to pause any extra processing associated with this view and
2194     * its associated DOM/plugins/javascript/etc. For example, if the view is
2195     * taken offscreen, this could be called to reduce unnecessary CPU and/or
2196     * network traffic. When the view is again "active", call onResume().
2197     *
2198     * Note that this differs from pauseTimers(), which affects all views/DOMs
2199     * @hide
2200     */
2201    public void onPause() {
2202        if (!mIsPaused) {
2203            mIsPaused = true;
2204            mWebViewCore.sendMessage(EventHub.ON_PAUSE);
2205        }
2206    }
2207
2208    /**
2209     * Call this to balanace a previous call to onPause()
2210     * @hide
2211     */
2212    public void onResume() {
2213        if (mIsPaused) {
2214            mIsPaused = false;
2215            mWebViewCore.sendMessage(EventHub.ON_RESUME);
2216        }
2217    }
2218
2219    /**
2220     * Returns true if the view is paused, meaning onPause() was called. Calling
2221     * onResume() sets the paused state back to false.
2222     * @hide
2223     */
2224    public boolean isPaused() {
2225        return mIsPaused;
2226    }
2227
2228    /**
2229     * Call this to inform the view that memory is low so that it can
2230     * free any available memory.
2231     * @hide
2232     */
2233    public void freeMemory() {
2234        mWebViewCore.sendMessage(EventHub.FREE_MEMORY);
2235    }
2236
2237    /**
2238     * Clear the resource cache. Note that the cache is per-application, so
2239     * this will clear the cache for all WebViews used.
2240     *
2241     * @param includeDiskFiles If false, only the RAM cache is cleared.
2242     */
2243    public void clearCache(boolean includeDiskFiles) {
2244        // Note: this really needs to be a static method as it clears cache for all
2245        // WebView. But we need mWebViewCore to send message to WebCore thread, so
2246        // we can't make this static.
2247        mWebViewCore.sendMessage(EventHub.CLEAR_CACHE,
2248                includeDiskFiles ? 1 : 0, 0);
2249    }
2250
2251    /**
2252     * Make sure that clearing the form data removes the adapter from the
2253     * currently focused textfield if there is one.
2254     */
2255    public void clearFormData() {
2256        if (inEditingMode()) {
2257            AutoCompleteAdapter adapter = null;
2258            mWebTextView.setAdapterCustom(adapter);
2259        }
2260    }
2261
2262    /**
2263     * Tell the WebView to clear its internal back/forward list.
2264     */
2265    public void clearHistory() {
2266        mCallbackProxy.getBackForwardList().setClearPending();
2267        mWebViewCore.sendMessage(EventHub.CLEAR_HISTORY);
2268    }
2269
2270    /**
2271     * Clear the SSL preferences table stored in response to proceeding with SSL
2272     * certificate errors.
2273     */
2274    public void clearSslPreferences() {
2275        mWebViewCore.sendMessage(EventHub.CLEAR_SSL_PREF_TABLE);
2276    }
2277
2278    /**
2279     * Return the WebBackForwardList for this WebView. This contains the
2280     * back/forward list for use in querying each item in the history stack.
2281     * This is a copy of the private WebBackForwardList so it contains only a
2282     * snapshot of the current state. Multiple calls to this method may return
2283     * different objects. The object returned from this method will not be
2284     * updated to reflect any new state.
2285     */
2286    public WebBackForwardList copyBackForwardList() {
2287        return mCallbackProxy.getBackForwardList().clone();
2288    }
2289
2290    /*
2291     * Highlight and scroll to the next occurance of String in findAll.
2292     * Wraps the page infinitely, and scrolls.  Must be called after
2293     * calling findAll.
2294     *
2295     * @param forward Direction to search.
2296     */
2297    public void findNext(boolean forward) {
2298        nativeFindNext(forward);
2299    }
2300
2301    /*
2302     * Find all instances of find on the page and highlight them.
2303     * @param find  String to find.
2304     * @return int  The number of occurances of the String "find"
2305     *              that were found.
2306     */
2307    public int findAll(String find) {
2308        mFindIsUp = true;
2309        int result = nativeFindAll(find.toLowerCase(), find.toUpperCase());
2310        invalidate();
2311        return result;
2312    }
2313
2314    // Used to know whether the find dialog is open.  Affects whether
2315    // or not we draw the highlights for matches.
2316    private boolean mFindIsUp;
2317
2318    /**
2319     * Return the first substring consisting of the address of a physical
2320     * location. Currently, only addresses in the United States are detected,
2321     * and consist of:
2322     * - a house number
2323     * - a street name
2324     * - a street type (Road, Circle, etc), either spelled out or abbreviated
2325     * - a city name
2326     * - a state or territory, either spelled out or two-letter abbr.
2327     * - an optional 5 digit or 9 digit zip code.
2328     *
2329     * All names must be correctly capitalized, and the zip code, if present,
2330     * must be valid for the state. The street type must be a standard USPS
2331     * spelling or abbreviation. The state or territory must also be spelled
2332     * or abbreviated using USPS standards. The house number may not exceed
2333     * five digits.
2334     * @param addr The string to search for addresses.
2335     *
2336     * @return the address, or if no address is found, return null.
2337     */
2338    public static String findAddress(String addr) {
2339        return findAddress(addr, false);
2340    }
2341
2342    /**
2343     * @hide
2344     * Return the first substring consisting of the address of a physical
2345     * location. Currently, only addresses in the United States are detected,
2346     * and consist of:
2347     * - a house number
2348     * - a street name
2349     * - a street type (Road, Circle, etc), either spelled out or abbreviated
2350     * - a city name
2351     * - a state or territory, either spelled out or two-letter abbr.
2352     * - an optional 5 digit or 9 digit zip code.
2353     *
2354     * Names are optionally capitalized, and the zip code, if present,
2355     * must be valid for the state. The street type must be a standard USPS
2356     * spelling or abbreviation. The state or territory must also be spelled
2357     * or abbreviated using USPS standards. The house number may not exceed
2358     * five digits.
2359     * @param addr The string to search for addresses.
2360     * @param caseInsensitive addr Set to true to make search ignore case.
2361     *
2362     * @return the address, or if no address is found, return null.
2363     */
2364    public static String findAddress(String addr, boolean caseInsensitive) {
2365        return WebViewCore.nativeFindAddress(addr, caseInsensitive);
2366    }
2367
2368    /*
2369     * Clear the highlighting surrounding text matches created by findAll.
2370     */
2371    public void clearMatches() {
2372        mFindIsUp = false;
2373        nativeSetFindIsDown();
2374        // Now that the dialog has been removed, ensure that we scroll to a
2375        // location that is not beyond the end of the page.
2376        pinScrollTo(mScrollX, mScrollY, false, 0);
2377        invalidate();
2378    }
2379
2380    /**
2381     * Query the document to see if it contains any image references. The
2382     * message object will be dispatched with arg1 being set to 1 if images
2383     * were found and 0 if the document does not reference any images.
2384     * @param response The message that will be dispatched with the result.
2385     */
2386    public void documentHasImages(Message response) {
2387        if (response == null) {
2388            return;
2389        }
2390        mWebViewCore.sendMessage(EventHub.DOC_HAS_IMAGES, response);
2391    }
2392
2393    @Override
2394    public void computeScroll() {
2395        if (mScroller.computeScrollOffset()) {
2396            int oldX = mScrollX;
2397            int oldY = mScrollY;
2398            mScrollX = mScroller.getCurrX();
2399            mScrollY = mScroller.getCurrY();
2400            postInvalidate();  // So we draw again
2401            if (oldX != mScrollX || oldY != mScrollY) {
2402                // as onScrollChanged() is not called, sendOurVisibleRect()
2403                // needs to be call explicitly
2404                sendOurVisibleRect();
2405            }
2406        } else {
2407            super.computeScroll();
2408        }
2409    }
2410
2411    private static int computeDuration(int dx, int dy) {
2412        int distance = Math.max(Math.abs(dx), Math.abs(dy));
2413        int duration = distance * 1000 / STD_SPEED;
2414        return Math.min(duration, MAX_DURATION);
2415    }
2416
2417    // helper to pin the scrollBy parameters (already in view coordinates)
2418    // returns true if the scroll was changed
2419    private boolean pinScrollBy(int dx, int dy, boolean animate, int animationDuration) {
2420        return pinScrollTo(mScrollX + dx, mScrollY + dy, animate, animationDuration);
2421    }
2422
2423    // helper to pin the scrollTo parameters (already in view coordinates)
2424    // returns true if the scroll was changed
2425    private boolean pinScrollTo(int x, int y, boolean animate, int animationDuration) {
2426        x = pinLocX(x);
2427        y = pinLocY(y);
2428        int dx = x - mScrollX;
2429        int dy = y - mScrollY;
2430
2431        if ((dx | dy) == 0) {
2432            return false;
2433        }
2434        // By this point we have added in the title bar's height.  If the site
2435        // is trying to scroll to the top of the page, scroll it to the top
2436        // of the WebView including showing the title bar.
2437        // mobile sites prefer to scroll to (0, 1), thus the + 1 below
2438        if (getVisibleTitleHeight() > 0 && x == 0
2439                && y <= getTitleHeight() + 1) {
2440            y = 0;
2441            animate = false;
2442        }
2443        if (animate) {
2444            //        Log.d(LOGTAG, "startScroll: " + dx + " " + dy);
2445            mScroller.startScroll(mScrollX, mScrollY, dx, dy,
2446                    animationDuration > 0 ? animationDuration : computeDuration(dx, dy));
2447            invalidate();
2448        } else {
2449            abortAnimation(); // just in case
2450            scrollTo(x, y);
2451        }
2452        return true;
2453    }
2454
2455    // Scale from content to view coordinates, and pin.
2456    // Also called by jni webview.cpp
2457    private boolean setContentScrollBy(int cx, int cy, boolean animate) {
2458        if (mDrawHistory) {
2459            // disallow WebView to change the scroll position as History Picture
2460            // is used in the view system.
2461            // TODO: as we switchOutDrawHistory when trackball or navigation
2462            // keys are hit, this should be safe. Right?
2463            return false;
2464        }
2465        cx = contentToViewDimension(cx);
2466        cy = contentToViewDimension(cy);
2467        if (mHeightCanMeasure) {
2468            // move our visible rect according to scroll request
2469            if (cy != 0) {
2470                Rect tempRect = new Rect();
2471                calcOurVisibleRect(tempRect);
2472                tempRect.offset(cx, cy);
2473                requestRectangleOnScreen(tempRect);
2474            }
2475            // FIXME: We scroll horizontally no matter what because currently
2476            // ScrollView and ListView will not scroll horizontally.
2477            // FIXME: Why do we only scroll horizontally if there is no
2478            // vertical scroll?
2479//                Log.d(LOGTAG, "setContentScrollBy cy=" + cy);
2480            return cy == 0 && cx != 0 && pinScrollBy(cx, 0, animate, 0);
2481        } else {
2482            return pinScrollBy(cx, cy, animate, 0);
2483        }
2484    }
2485
2486    // scale from content to view coordinates, and pin
2487    // return true if pin caused the final x/y different than the request cx/cy,
2488    // and a future scroll may reach the request cx/cy after our size has
2489    // changed
2490    // return false if the view scroll to the exact position as it is requested,
2491    // where negative numbers are taken to mean 0
2492    private boolean setContentScrollTo(int cx, int cy) {
2493        if (mDrawHistory) {
2494            // disallow WebView to change the scroll position as History Picture
2495            // is used in the view system.
2496            // One known case where this is called is that WebCore tries to
2497            // restore the scroll position. As history Picture already uses the
2498            // saved scroll position, it is ok to skip this.
2499            return false;
2500        }
2501        int vx = contentToViewX(cx);
2502        int vy = contentToViewY(cy);
2503//        Log.d(LOGTAG, "content scrollTo [" + cx + " " + cy + "] view=[" +
2504//                      vx + " " + vy + "]");
2505        pinScrollTo(vx, vy, false, 0);
2506        // If the request was to scroll to a negative coordinate, treat it as if
2507        // it was a request to scroll to 0
2508        if ((mScrollX != vx && cx >= 0) || (mScrollY != vy && cy >= 0)) {
2509            return true;
2510        } else {
2511            return false;
2512        }
2513    }
2514
2515    // scale from content to view coordinates, and pin
2516    private void spawnContentScrollTo(int cx, int cy) {
2517        if (mDrawHistory) {
2518            // disallow WebView to change the scroll position as History Picture
2519            // is used in the view system.
2520            return;
2521        }
2522        int vx = contentToViewX(cx);
2523        int vy = contentToViewY(cy);
2524        pinScrollTo(vx, vy, true, 0);
2525    }
2526
2527    /**
2528     * These are from webkit, and are in content coordinate system (unzoomed)
2529     */
2530    private void contentSizeChanged(boolean updateLayout) {
2531        // suppress 0,0 since we usually see real dimensions soon after
2532        // this avoids drawing the prev content in a funny place. If we find a
2533        // way to consolidate these notifications, this check may become
2534        // obsolete
2535        if ((mContentWidth | mContentHeight) == 0) {
2536            return;
2537        }
2538
2539        if (mHeightCanMeasure) {
2540            if (getMeasuredHeight() != contentToViewDimension(mContentHeight)
2541                    && updateLayout) {
2542                requestLayout();
2543            }
2544        } else if (mWidthCanMeasure) {
2545            if (getMeasuredWidth() != contentToViewDimension(mContentWidth)
2546                    && updateLayout) {
2547                requestLayout();
2548            }
2549        } else {
2550            // If we don't request a layout, try to send our view size to the
2551            // native side to ensure that WebCore has the correct dimensions.
2552            sendViewSizeZoom();
2553        }
2554    }
2555
2556    /**
2557     * Set the WebViewClient that will receive various notifications and
2558     * requests. This will replace the current handler.
2559     * @param client An implementation of WebViewClient.
2560     */
2561    public void setWebViewClient(WebViewClient client) {
2562        mCallbackProxy.setWebViewClient(client);
2563    }
2564
2565    /**
2566     * Register the interface to be used when content can not be handled by
2567     * the rendering engine, and should be downloaded instead. This will replace
2568     * the current handler.
2569     * @param listener An implementation of DownloadListener.
2570     */
2571    public void setDownloadListener(DownloadListener listener) {
2572        mCallbackProxy.setDownloadListener(listener);
2573    }
2574
2575    /**
2576     * Set the chrome handler. This is an implementation of WebChromeClient for
2577     * use in handling Javascript dialogs, favicons, titles, and the progress.
2578     * This will replace the current handler.
2579     * @param client An implementation of WebChromeClient.
2580     */
2581    public void setWebChromeClient(WebChromeClient client) {
2582        mCallbackProxy.setWebChromeClient(client);
2583    }
2584
2585    /**
2586     * Gets the chrome handler.
2587     * @return the current WebChromeClient instance.
2588     *
2589     * @hide API council approval.
2590     */
2591    public WebChromeClient getWebChromeClient() {
2592        return mCallbackProxy.getWebChromeClient();
2593    }
2594
2595    /**
2596     * Set the Picture listener. This is an interface used to receive
2597     * notifications of a new Picture.
2598     * @param listener An implementation of WebView.PictureListener.
2599     */
2600    public void setPictureListener(PictureListener listener) {
2601        mPictureListener = listener;
2602    }
2603
2604    /**
2605     * {@hide}
2606     */
2607    /* FIXME: Debug only! Remove for SDK! */
2608    public void externalRepresentation(Message callback) {
2609        mWebViewCore.sendMessage(EventHub.REQUEST_EXT_REPRESENTATION, callback);
2610    }
2611
2612    /**
2613     * {@hide}
2614     */
2615    /* FIXME: Debug only! Remove for SDK! */
2616    public void documentAsText(Message callback) {
2617        mWebViewCore.sendMessage(EventHub.REQUEST_DOC_AS_TEXT, callback);
2618    }
2619
2620    /**
2621     * Use this function to bind an object to Javascript so that the
2622     * methods can be accessed from Javascript.
2623     * <p><strong>IMPORTANT:</strong>
2624     * <ul>
2625     * <li> Using addJavascriptInterface() allows JavaScript to control your
2626     * application. This can be a very useful feature or a dangerous security
2627     * issue. When the HTML in the WebView is untrustworthy (for example, part
2628     * or all of the HTML is provided by some person or process), then an
2629     * attacker could inject HTML that will execute your code and possibly any
2630     * code of the attacker's choosing.<br>
2631     * Do not use addJavascriptInterface() unless all of the HTML in this
2632     * WebView was written by you.</li>
2633     * <li> The Java object that is bound runs in another thread and not in
2634     * the thread that it was constructed in.</li>
2635     * </ul></p>
2636     * @param obj The class instance to bind to Javascript
2637     * @param interfaceName The name to used to expose the class in Javascript
2638     */
2639    public void addJavascriptInterface(Object obj, String interfaceName) {
2640        WebViewCore.JSInterfaceData arg = new WebViewCore.JSInterfaceData();
2641        arg.mObject = obj;
2642        arg.mInterfaceName = interfaceName;
2643        mWebViewCore.sendMessage(EventHub.ADD_JS_INTERFACE, arg);
2644    }
2645
2646    /**
2647     * Return the WebSettings object used to control the settings for this
2648     * WebView.
2649     * @return A WebSettings object that can be used to control this WebView's
2650     *         settings.
2651     */
2652    public WebSettings getSettings() {
2653        return mWebViewCore.getSettings();
2654    }
2655
2656   /**
2657    * Return the list of currently loaded plugins.
2658    * @return The list of currently loaded plugins.
2659    *
2660    * @deprecated This was used for Gears, which has been deprecated.
2661    */
2662    @Deprecated
2663    public static synchronized PluginList getPluginList() {
2664        return null;
2665    }
2666
2667   /**
2668    * @deprecated This was used for Gears, which has been deprecated.
2669    */
2670    @Deprecated
2671    public void refreshPlugins(boolean reloadOpenPages) { }
2672
2673    //-------------------------------------------------------------------------
2674    // Override View methods
2675    //-------------------------------------------------------------------------
2676
2677    @Override
2678    protected void finalize() throws Throwable {
2679        try {
2680            destroy();
2681        } finally {
2682            super.finalize();
2683        }
2684    }
2685
2686    @Override
2687    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
2688        if (child == mTitleBar) {
2689            // When drawing the title bar, move it horizontally to always show
2690            // at the top of the WebView.
2691            mTitleBar.offsetLeftAndRight(mScrollX - mTitleBar.getLeft());
2692        }
2693        return super.drawChild(canvas, child, drawingTime);
2694    }
2695
2696    @Override
2697    protected void onDraw(Canvas canvas) {
2698        // if mNativeClass is 0, the WebView has been destroyed. Do nothing.
2699        if (mNativeClass == 0) {
2700            return;
2701        }
2702        int saveCount = canvas.save();
2703        if (mTitleBar != null) {
2704            canvas.translate(0, (int) mTitleBar.getHeight());
2705        }
2706        // Update the buttons in the picture, so when we draw the picture
2707        // to the screen, they are in the correct state.
2708        // Tell the native side if user is a) touching the screen,
2709        // b) pressing the trackball down, or c) pressing the enter key
2710        // If the cursor is on a button, we need to draw it in the pressed
2711        // state.
2712        // If mNativeClass is 0, we should not reach here, so we do not
2713        // need to check it again.
2714        nativeRecordButtons(hasFocus() && hasWindowFocus(),
2715                mTouchMode == TOUCH_SHORTPRESS_START_MODE
2716                || mTrackballDown || mGotCenterDown, false);
2717        drawCoreAndCursorRing(canvas, mBackgroundColor, mDrawCursorRing);
2718        canvas.restoreToCount(saveCount);
2719
2720        // Now draw the shadow.
2721        if (mTitleBar != null) {
2722            int y = mScrollY + getVisibleTitleHeight();
2723            int height = (int) (5f * getContext().getResources()
2724                    .getDisplayMetrics().density);
2725            mTitleShadow.setBounds(mScrollX, y, mScrollX + getWidth(),
2726                    y + height);
2727            mTitleShadow.draw(canvas);
2728        }
2729        if (AUTO_REDRAW_HACK && mAutoRedraw) {
2730            invalidate();
2731        }
2732    }
2733
2734    @Override
2735    public void setLayoutParams(ViewGroup.LayoutParams params) {
2736        if (params.height == LayoutParams.WRAP_CONTENT) {
2737            mWrapContent = true;
2738        }
2739        super.setLayoutParams(params);
2740    }
2741
2742    @Override
2743    public boolean performLongClick() {
2744        if (mNativeClass != 0 && nativeCursorIsTextInput()) {
2745            // Send the click so that the textfield is in focus
2746            // FIXME: When we start respecting changes to the native textfield's
2747            // selection, need to make sure that this does not change it.
2748            mWebViewCore.sendMessage(EventHub.CLICK, nativeCursorFramePointer(),
2749                    nativeCursorNodePointer());
2750            rebuildWebTextView();
2751        }
2752        if (inEditingMode()) {
2753            return mWebTextView.performLongClick();
2754        } else {
2755            return super.performLongClick();
2756        }
2757    }
2758
2759    /**
2760     * Need to adjust the WebTextView after a change in zoom, since mActualScale
2761     * has changed.  This is especially important for password fields, which are
2762     * drawn by the WebTextView, since it conveys more information than what
2763     * webkit draws.  Thus we need to reposition it to show in the correct
2764     * place.
2765     */
2766    private boolean mNeedToAdjustWebTextView;
2767
2768    private void drawCoreAndCursorRing(Canvas canvas, int color,
2769        boolean drawCursorRing) {
2770        if (mDrawHistory) {
2771            canvas.scale(mActualScale, mActualScale);
2772            canvas.drawPicture(mHistoryPicture);
2773            return;
2774        }
2775
2776        boolean animateZoom = mZoomScale != 0;
2777        boolean animateScroll = !mScroller.isFinished()
2778                || mVelocityTracker != null;
2779        if (animateZoom) {
2780            float zoomScale;
2781            int interval = (int) (SystemClock.uptimeMillis() - mZoomStart);
2782            if (interval < ZOOM_ANIMATION_LENGTH) {
2783                float ratio = (float) interval / ZOOM_ANIMATION_LENGTH;
2784                zoomScale = 1.0f / (mInvInitialZoomScale
2785                        + (mInvFinalZoomScale - mInvInitialZoomScale) * ratio);
2786                invalidate();
2787            } else {
2788                zoomScale = mZoomScale;
2789                // set mZoomScale to be 0 as we have done animation
2790                mZoomScale = 0;
2791                // call invalidate() again to draw with the final filters
2792                invalidate();
2793                if (mNeedToAdjustWebTextView) {
2794                    mNeedToAdjustWebTextView = false;
2795                    mWebTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
2796                            contentToViewDimension(
2797                            nativeFocusCandidateTextSize()));
2798                    Rect bounds = nativeFocusCandidateNodeBounds();
2799                    Rect vBox = contentToViewRect(bounds);
2800                    mWebTextView.setRect(vBox.left, vBox.top, vBox.width(),
2801                            vBox.height());
2802                    // If it is a password field, start drawing the
2803                    // WebTextView once again.
2804                    if (nativeFocusCandidateIsPassword()) {
2805                        mWebTextView.setInPassword(true);
2806                    }
2807                }
2808            }
2809            // calculate the intermediate scroll position. As we need to use
2810            // zoomScale, we can't use pinLocX/Y directly. Copy the logic here.
2811            float scale = zoomScale * mInvInitialZoomScale;
2812            int tx = Math.round(scale * (mInitialScrollX + mZoomCenterX)
2813                    - mZoomCenterX);
2814            tx = -pinLoc(tx, getViewWidth(), Math.round(mContentWidth
2815                    * zoomScale)) + mScrollX;
2816            int titleHeight = getTitleHeight();
2817            int ty = Math.round(scale
2818                    * (mInitialScrollY + mZoomCenterY - titleHeight)
2819                    - (mZoomCenterY - titleHeight));
2820            ty = -(ty <= titleHeight ? Math.max(ty, 0) : pinLoc(ty
2821                    - titleHeight, getViewHeight(), Math.round(mContentHeight
2822                    * zoomScale)) + titleHeight) + mScrollY;
2823            canvas.translate(tx, ty);
2824            canvas.scale(zoomScale, zoomScale);
2825            if (inEditingMode() && !mNeedToAdjustWebTextView
2826                    && mZoomScale != 0) {
2827                // The WebTextView is up.  Keep track of this so we can adjust
2828                // its size and placement when we finish zooming
2829                mNeedToAdjustWebTextView = true;
2830                // If it is in password mode, turn it off so it does not draw
2831                // misplaced.
2832                if (nativeFocusCandidateIsPassword()) {
2833                    mWebTextView.setInPassword(false);
2834                }
2835            }
2836        } else {
2837            canvas.scale(mActualScale, mActualScale);
2838        }
2839
2840        mWebViewCore.drawContentPicture(canvas, color, animateZoom,
2841                animateScroll);
2842
2843        if (mNativeClass == 0) return;
2844        if (mShiftIsPressed) {
2845            if (mTouchSelection) {
2846                nativeDrawSelectionRegion(canvas);
2847            } else {
2848                nativeDrawSelection(canvas, mSelectX, mSelectY,
2849                        mExtendSelection);
2850            }
2851        } else if (drawCursorRing) {
2852            if (mTouchMode == TOUCH_SHORTPRESS_START_MODE) {
2853                mTouchMode = TOUCH_SHORTPRESS_MODE;
2854                HitTestResult hitTest = getHitTestResult();
2855                if (hitTest != null &&
2856                        hitTest.mType != HitTestResult.UNKNOWN_TYPE) {
2857                    mPrivateHandler.sendMessageDelayed(mPrivateHandler
2858                            .obtainMessage(SWITCH_TO_LONGPRESS),
2859                            LONG_PRESS_TIMEOUT);
2860                }
2861            }
2862            nativeDrawCursorRing(canvas);
2863        }
2864        // When the FindDialog is up, only draw the matches if we are not in
2865        // the process of scrolling them into view.
2866        if (mFindIsUp && !animateScroll) {
2867            nativeDrawMatches(canvas);
2868        }
2869    }
2870
2871    // draw history
2872    private boolean mDrawHistory = false;
2873    private Picture mHistoryPicture = null;
2874    private int mHistoryWidth = 0;
2875    private int mHistoryHeight = 0;
2876
2877    // Only check the flag, can be called from WebCore thread
2878    boolean drawHistory() {
2879        return mDrawHistory;
2880    }
2881
2882    // Should only be called in UI thread
2883    void switchOutDrawHistory() {
2884        if (null == mWebViewCore) return; // CallbackProxy may trigger this
2885        if (mDrawHistory && mWebViewCore.pictureReady()) {
2886            mDrawHistory = false;
2887            invalidate();
2888            int oldScrollX = mScrollX;
2889            int oldScrollY = mScrollY;
2890            mScrollX = pinLocX(mScrollX);
2891            mScrollY = pinLocY(mScrollY);
2892            if (oldScrollX != mScrollX || oldScrollY != mScrollY) {
2893                mUserScroll = false;
2894                mWebViewCore.sendMessage(EventHub.SYNC_SCROLL, oldScrollX,
2895                        oldScrollY);
2896            }
2897            sendOurVisibleRect();
2898        }
2899    }
2900
2901    WebViewCore.CursorData cursorData() {
2902        WebViewCore.CursorData result = new WebViewCore.CursorData();
2903        result.mMoveGeneration = nativeMoveGeneration();
2904        result.mFrame = nativeCursorFramePointer();
2905        Point position = nativeCursorPosition();
2906        result.mX = position.x;
2907        result.mY = position.y;
2908        return result;
2909    }
2910
2911    /**
2912     *  Delete text from start to end in the focused textfield. If there is no
2913     *  focus, or if start == end, silently fail.  If start and end are out of
2914     *  order, swap them.
2915     *  @param  start   Beginning of selection to delete.
2916     *  @param  end     End of selection to delete.
2917     */
2918    /* package */ void deleteSelection(int start, int end) {
2919        mTextGeneration++;
2920        WebViewCore.TextSelectionData data
2921                = new WebViewCore.TextSelectionData(start, end);
2922        mWebViewCore.sendMessage(EventHub.DELETE_SELECTION, mTextGeneration, 0,
2923                data);
2924    }
2925
2926    /**
2927     *  Set the selection to (start, end) in the focused textfield. If start and
2928     *  end are out of order, swap them.
2929     *  @param  start   Beginning of selection.
2930     *  @param  end     End of selection.
2931     */
2932    /* package */ void setSelection(int start, int end) {
2933        mWebViewCore.sendMessage(EventHub.SET_SELECTION, start, end);
2934    }
2935
2936    // Called by JNI when a touch event puts a textfield into focus.
2937    private void displaySoftKeyboard(boolean isTextView) {
2938        InputMethodManager imm = (InputMethodManager)
2939                getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
2940
2941        if (isTextView) {
2942            if (mWebTextView == null) return;
2943
2944            imm.showSoftInput(mWebTextView, 0);
2945            // Now we need to fake a touch event to place the cursor where the
2946            // user touched.
2947            AbsoluteLayout.LayoutParams lp = (AbsoluteLayout.LayoutParams)
2948                    mWebTextView.getLayoutParams();
2949            if (lp != null) {
2950                // Take the last touch and adjust for the location of the
2951                // WebTextView.
2952                float x = mLastTouchX + (float) (mScrollX - lp.x);
2953                float y = mLastTouchY + (float) (mScrollY - lp.y);
2954                mWebTextView.fakeTouchEvent(x, y);
2955            }
2956            if (mInZoomOverview) {
2957                // if in zoom overview mode, call doDoubleTap() to bring it back
2958                // to normal mode so that user can enter text.
2959                doDoubleTap();
2960            }
2961        }
2962        else { // used by plugins
2963            imm.showSoftInput(this, 0);
2964        }
2965    }
2966
2967    // Called by WebKit to instruct the UI to hide the keyboard
2968    private void hideSoftKeyboard() {
2969        InputMethodManager imm = (InputMethodManager)
2970                getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
2971
2972        imm.hideSoftInputFromWindow(this.getWindowToken(), 0);
2973    }
2974
2975    /*
2976     * This method checks the current focus and cursor and potentially rebuilds
2977     * mWebTextView to have the appropriate properties, such as password,
2978     * multiline, and what text it contains.  It also removes it if necessary.
2979     */
2980    /* package */ void rebuildWebTextView() {
2981        // If the WebView does not have focus, do nothing until it gains focus.
2982        if (!hasFocus() && (null == mWebTextView || !mWebTextView.hasFocus())) {
2983            return;
2984        }
2985        boolean alreadyThere = inEditingMode();
2986        // inEditingMode can only return true if mWebTextView is non-null,
2987        // so we can safely call remove() if (alreadyThere)
2988        if (0 == mNativeClass || !nativeFocusCandidateIsTextInput()) {
2989            if (alreadyThere) {
2990                mWebTextView.remove();
2991            }
2992            return;
2993        }
2994        // At this point, we know we have found an input field, so go ahead
2995        // and create the WebTextView if necessary.
2996        if (mWebTextView == null) {
2997            mWebTextView = new WebTextView(mContext, WebView.this);
2998            // Initialize our generation number.
2999            mTextGeneration = 0;
3000        }
3001        mWebTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
3002                contentToViewDimension(nativeFocusCandidateTextSize()));
3003        Rect visibleRect = new Rect();
3004        calcOurContentVisibleRect(visibleRect);
3005        // Note that sendOurVisibleRect calls viewToContent, so the coordinates
3006        // should be in content coordinates.
3007        Rect bounds = nativeFocusCandidateNodeBounds();
3008        if (!Rect.intersects(bounds, visibleRect)) {
3009            mWebTextView.bringIntoView();
3010        }
3011        String text = nativeFocusCandidateText();
3012        int nodePointer = nativeFocusCandidatePointer();
3013        if (alreadyThere && mWebTextView.isSameTextField(nodePointer)) {
3014            // It is possible that we have the same textfield, but it has moved,
3015            // i.e. In the case of opening/closing the screen.
3016            // In that case, we need to set the dimensions, but not the other
3017            // aspects.
3018            // We also need to restore the selection, which gets wrecked by
3019            // calling setTextEntryRect.
3020            Spannable spannable = (Spannable) mWebTextView.getText();
3021            int start = Selection.getSelectionStart(spannable);
3022            int end = Selection.getSelectionEnd(spannable);
3023            // If the text has been changed by webkit, update it.  However, if
3024            // there has been more UI text input, ignore it.  We will receive
3025            // another update when that text is recognized.
3026            if (text != null && !text.equals(spannable.toString())
3027                    && nativeTextGeneration() == mTextGeneration) {
3028                mWebTextView.setTextAndKeepSelection(text);
3029            } else {
3030                Selection.setSelection(spannable, start, end);
3031            }
3032        } else {
3033            Rect vBox = contentToViewRect(bounds);
3034            mWebTextView.setRect(vBox.left, vBox.top, vBox.width(),
3035                    vBox.height());
3036            mWebTextView.setGravity(nativeFocusCandidateIsRtlText() ?
3037                    Gravity.RIGHT : Gravity.NO_GRAVITY);
3038            // this needs to be called before update adapter thread starts to
3039            // ensure the mWebTextView has the same node pointer
3040            mWebTextView.setNodePointer(nodePointer);
3041            int maxLength = -1;
3042            boolean isTextField = nativeFocusCandidateIsTextField();
3043            if (isTextField) {
3044                maxLength = nativeFocusCandidateMaxLength();
3045                String name = nativeFocusCandidateName();
3046                if (mWebViewCore.getSettings().getSaveFormData()
3047                        && name != null) {
3048                    Message update = mPrivateHandler.obtainMessage(
3049                            REQUEST_FORM_DATA, nodePointer);
3050                    RequestFormData updater = new RequestFormData(name,
3051                            getUrl(), update);
3052                    Thread t = new Thread(updater);
3053                    t.start();
3054                }
3055            }
3056            mWebTextView.setMaxLength(maxLength);
3057            AutoCompleteAdapter adapter = null;
3058            mWebTextView.setAdapterCustom(adapter);
3059            mWebTextView.setSingleLine(isTextField);
3060            mWebTextView.setInPassword(nativeFocusCandidateIsPassword());
3061            if (null == text) {
3062                mWebTextView.setText("", 0, 0);
3063                if (DebugFlags.WEB_VIEW) {
3064                    Log.v(LOGTAG, "rebuildWebTextView null == text");
3065                }
3066            } else {
3067                // Change to true to enable the old style behavior, where
3068                // entering a textfield/textarea always set the selection to the
3069                // whole field.  This was desirable for the case where the user
3070                // intends to scroll past the field using the trackball.
3071                // However, it causes a problem when replying to emails - the
3072                // user expects the cursor to be at the beginning of the
3073                // textarea.  Testing out a new behavior, where textfields set
3074                // selection at the end, and textareas at the beginning.
3075                if (false) {
3076                    mWebTextView.setText(text, 0, text.length());
3077                } else if (isTextField) {
3078                    int length = text.length();
3079                    mWebTextView.setText(text, length, length);
3080                    if (DebugFlags.WEB_VIEW) {
3081                        Log.v(LOGTAG, "rebuildWebTextView length=" + length);
3082                    }
3083                } else {
3084                    mWebTextView.setText(text, 0, 0);
3085                    if (DebugFlags.WEB_VIEW) {
3086                        Log.v(LOGTAG, "rebuildWebTextView !isTextField");
3087                    }
3088                }
3089            }
3090            mWebTextView.requestFocus();
3091        }
3092    }
3093
3094    /*
3095     * This class requests an Adapter for the WebTextView which shows past
3096     * entries stored in the database.  It is a Runnable so that it can be done
3097     * in its own thread, without slowing down the UI.
3098     */
3099    private class RequestFormData implements Runnable {
3100        private String mName;
3101        private String mUrl;
3102        private Message mUpdateMessage;
3103
3104        public RequestFormData(String name, String url, Message msg) {
3105            mName = name;
3106            mUrl = url;
3107            mUpdateMessage = msg;
3108        }
3109
3110        public void run() {
3111            ArrayList<String> pastEntries = mDatabase.getFormData(mUrl, mName);
3112            if (pastEntries.size() > 0) {
3113                AutoCompleteAdapter adapter = new
3114                        AutoCompleteAdapter(mContext, pastEntries);
3115                mUpdateMessage.obj = adapter;
3116                mUpdateMessage.sendToTarget();
3117            }
3118        }
3119    }
3120
3121    // This is used to determine long press with the center key.  Does not
3122    // affect long press with the trackball/touch.
3123    private boolean mGotCenterDown = false;
3124
3125    @Override
3126    public boolean onKeyDown(int keyCode, KeyEvent event) {
3127        if (DebugFlags.WEB_VIEW) {
3128            Log.v(LOGTAG, "keyDown at " + System.currentTimeMillis()
3129                    + ", " + event + ", unicode=" + event.getUnicodeChar());
3130        }
3131
3132        if (mNativeClass == 0) {
3133            return false;
3134        }
3135
3136        // do this hack up front, so it always works, regardless of touch-mode
3137        if (AUTO_REDRAW_HACK && (keyCode == KeyEvent.KEYCODE_CALL)) {
3138            mAutoRedraw = !mAutoRedraw;
3139            if (mAutoRedraw) {
3140                invalidate();
3141            }
3142            return true;
3143        }
3144
3145        // Bubble up the key event if
3146        // 1. it is a system key; or
3147        // 2. the host application wants to handle it;
3148        if (event.isSystem()
3149                || mCallbackProxy.uiOverrideKeyEvent(event)) {
3150            return false;
3151        }
3152
3153        if (mShiftIsPressed == false && nativeCursorWantsKeyEvents() == false
3154                && (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
3155                || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT)) {
3156            mExtendSelection = false;
3157            mShiftIsPressed = true;
3158            if (nativeHasCursorNode()) {
3159                Rect rect = nativeCursorNodeBounds();
3160                mSelectX = contentToViewX(rect.left);
3161                mSelectY = contentToViewY(rect.top);
3162            } else {
3163                mSelectX = mScrollX + (int) mLastTouchX;
3164                mSelectY = mScrollY + (int) mLastTouchY;
3165            }
3166            nativeHideCursor();
3167       }
3168
3169        if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
3170                && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
3171            // always handle the navigation keys in the UI thread
3172            switchOutDrawHistory();
3173            if (navHandledKey(keyCode, 1, false, event.getEventTime(), false)) {
3174                playSoundEffect(keyCodeToSoundsEffect(keyCode));
3175                return true;
3176            }
3177            // Bubble up the key event as WebView doesn't handle it
3178            return false;
3179        }
3180
3181        if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
3182            switchOutDrawHistory();
3183            if (event.getRepeatCount() == 0) {
3184                mGotCenterDown = true;
3185                mPrivateHandler.sendMessageDelayed(mPrivateHandler
3186                        .obtainMessage(LONG_PRESS_CENTER), LONG_PRESS_TIMEOUT);
3187                // Already checked mNativeClass, so we do not need to check it
3188                // again.
3189                nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true);
3190                return true;
3191            }
3192            // Bubble up the key event as WebView doesn't handle it
3193            return false;
3194        }
3195
3196        if (keyCode != KeyEvent.KEYCODE_SHIFT_LEFT
3197                && keyCode != KeyEvent.KEYCODE_SHIFT_RIGHT) {
3198            // turn off copy select if a shift-key combo is pressed
3199            mExtendSelection = mShiftIsPressed = false;
3200            if (mTouchMode == TOUCH_SELECT_MODE) {
3201                mTouchMode = TOUCH_INIT_MODE;
3202            }
3203        }
3204
3205        if (getSettings().getNavDump()) {
3206            switch (keyCode) {
3207                case KeyEvent.KEYCODE_4:
3208                    // "/data/data/com.android.browser/displayTree.txt"
3209                    nativeDumpDisplayTree(getUrl());
3210                    break;
3211                case KeyEvent.KEYCODE_5:
3212                case KeyEvent.KEYCODE_6:
3213                    // 5: dump the dom tree to the file
3214                    // "/data/data/com.android.browser/domTree.txt"
3215                    // 6: dump the dom tree to the adb log
3216                    mWebViewCore.sendMessage(EventHub.DUMP_DOMTREE,
3217                            (keyCode == KeyEvent.KEYCODE_5) ? 1 : 0, 0);
3218                    break;
3219                case KeyEvent.KEYCODE_7:
3220                case KeyEvent.KEYCODE_8:
3221                    // 7: dump the render tree to the file
3222                    // "/data/data/com.android.browser/renderTree.txt"
3223                    // 8: dump the render tree to the adb log
3224                    mWebViewCore.sendMessage(EventHub.DUMP_RENDERTREE,
3225                            (keyCode == KeyEvent.KEYCODE_7) ? 1 : 0, 0);
3226                    break;
3227                case KeyEvent.KEYCODE_9:
3228                    nativeInstrumentReport();
3229                    return true;
3230            }
3231        }
3232
3233        if (nativeCursorIsPlugin()) {
3234            nativeUpdatePluginReceivesEvents();
3235            invalidate();
3236        } else if (nativeCursorIsTextInput()) {
3237            // This message will put the node in focus, for the DOM's notion
3238            // of focus, and make the focuscontroller active
3239            mWebViewCore.sendMessage(EventHub.CLICK, nativeCursorFramePointer(),
3240                    nativeCursorNodePointer());
3241            // This will bring up the WebTextView and put it in focus, for
3242            // our view system's notion of focus
3243            rebuildWebTextView();
3244            // Now we need to pass the event to it
3245            return mWebTextView.onKeyDown(keyCode, event);
3246        } else if (nativeHasFocusNode()) {
3247            // In this case, the cursor is not on a text input, but the focus
3248            // might be.  Check it, and if so, hand over to the WebTextView.
3249            rebuildWebTextView();
3250            if (inEditingMode()) {
3251                return mWebTextView.onKeyDown(keyCode, event);
3252            }
3253        }
3254
3255        // TODO: should we pass all the keys to DOM or check the meta tag
3256        if (nativeCursorWantsKeyEvents() || true) {
3257            // pass the key to DOM
3258            mWebViewCore.sendMessage(EventHub.KEY_DOWN, event);
3259            // return true as DOM handles the key
3260            return true;
3261        }
3262
3263        // Bubble up the key event as WebView doesn't handle it
3264        return false;
3265    }
3266
3267    @Override
3268    public boolean onKeyUp(int keyCode, KeyEvent event) {
3269        if (DebugFlags.WEB_VIEW) {
3270            Log.v(LOGTAG, "keyUp at " + System.currentTimeMillis()
3271                    + ", " + event + ", unicode=" + event.getUnicodeChar());
3272        }
3273
3274        if (mNativeClass == 0) {
3275            return false;
3276        }
3277
3278        // special CALL handling when cursor node's href is "tel:XXX"
3279        if (keyCode == KeyEvent.KEYCODE_CALL && nativeHasCursorNode()) {
3280            String text = nativeCursorText();
3281            if (!nativeCursorIsTextInput() && text != null
3282                    && text.startsWith(SCHEME_TEL)) {
3283                Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(text));
3284                getContext().startActivity(intent);
3285                return true;
3286            }
3287        }
3288
3289        // Bubble up the key event if
3290        // 1. it is a system key; or
3291        // 2. the host application wants to handle it;
3292        if (event.isSystem() || mCallbackProxy.uiOverrideKeyEvent(event)) {
3293            return false;
3294        }
3295
3296        if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
3297                || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
3298            if (commitCopy()) {
3299                return true;
3300            }
3301        }
3302
3303        if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
3304                && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
3305            // always handle the navigation keys in the UI thread
3306            // Bubble up the key event as WebView doesn't handle it
3307            return false;
3308        }
3309
3310        if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
3311            // remove the long press message first
3312            mPrivateHandler.removeMessages(LONG_PRESS_CENTER);
3313            mGotCenterDown = false;
3314
3315            if (mShiftIsPressed) {
3316                return false;
3317            }
3318
3319            // perform the single click
3320            Rect visibleRect = sendOurVisibleRect();
3321            // Note that sendOurVisibleRect calls viewToContent, so the
3322            // coordinates should be in content coordinates.
3323            if (!nativeCursorIntersects(visibleRect)) {
3324                return false;
3325            }
3326            nativeSetFollowedLink(true);
3327            nativeUpdatePluginReceivesEvents();
3328            WebViewCore.CursorData data = cursorData();
3329            mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, data);
3330            playSoundEffect(SoundEffectConstants.CLICK);
3331            boolean isTextInput = nativeCursorIsTextInput();
3332            if (isTextInput || !mCallbackProxy.uiOverrideUrlLoading(
3333                        nativeCursorText())) {
3334                mWebViewCore.sendMessage(EventHub.CLICK, data.mFrame,
3335                        nativeCursorNodePointer());
3336            }
3337            if (isTextInput) {
3338                rebuildWebTextView();
3339            }
3340            return true;
3341        }
3342
3343        // TODO: should we pass all the keys to DOM or check the meta tag
3344        if (nativeCursorWantsKeyEvents() || true) {
3345            // pass the key to DOM
3346            mWebViewCore.sendMessage(EventHub.KEY_UP, event);
3347            // return true as DOM handles the key
3348            return true;
3349        }
3350
3351        // Bubble up the key event as WebView doesn't handle it
3352        return false;
3353    }
3354
3355    /**
3356     * @hide
3357     */
3358    public void emulateShiftHeld() {
3359        mExtendSelection = false;
3360        mShiftIsPressed = true;
3361        nativeHideCursor();
3362    }
3363
3364    private boolean commitCopy() {
3365        boolean copiedSomething = false;
3366        if (mExtendSelection) {
3367            // copy region so core operates on copy without touching orig.
3368            Region selection = new Region(nativeGetSelection());
3369            if (selection.isEmpty() == false) {
3370                Toast.makeText(mContext
3371                        , com.android.internal.R.string.text_copied
3372                        , Toast.LENGTH_SHORT).show();
3373                mWebViewCore.sendMessage(EventHub.GET_SELECTION, selection);
3374                copiedSomething = true;
3375            }
3376            mExtendSelection = false;
3377        }
3378        mShiftIsPressed = false;
3379        if (mTouchMode == TOUCH_SELECT_MODE) {
3380            mTouchMode = TOUCH_INIT_MODE;
3381        }
3382        return copiedSomething;
3383    }
3384
3385    // Set this as a hierarchy change listener so we can know when this view
3386    // is removed and still have access to our parent.
3387    @Override
3388    protected void onAttachedToWindow() {
3389        super.onAttachedToWindow();
3390        ViewParent parent = getParent();
3391        if (parent instanceof ViewGroup) {
3392            ViewGroup p = (ViewGroup) parent;
3393            p.setOnHierarchyChangeListener(this);
3394        }
3395    }
3396
3397    @Override
3398    protected void onDetachedFromWindow() {
3399        super.onDetachedFromWindow();
3400        ViewParent parent = getParent();
3401        if (parent instanceof ViewGroup) {
3402            ViewGroup p = (ViewGroup) parent;
3403            p.setOnHierarchyChangeListener(null);
3404        }
3405
3406        // Clean up the zoom controller
3407        mZoomButtonsController.setVisible(false);
3408    }
3409
3410    // Implementation for OnHierarchyChangeListener
3411    public void onChildViewAdded(View parent, View child) {}
3412
3413    public void onChildViewRemoved(View p, View child) {
3414        if (child == this) {
3415            clearTextEntry();
3416        }
3417    }
3418
3419    /**
3420     * @deprecated WebView should not have implemented
3421     * ViewTreeObserver.OnGlobalFocusChangeListener.  This method
3422     * does nothing now.
3423     */
3424    @Deprecated
3425    public void onGlobalFocusChanged(View oldFocus, View newFocus) {
3426    }
3427
3428    // To avoid drawing the cursor ring, and remove the TextView when our window
3429    // loses focus.
3430    @Override
3431    public void onWindowFocusChanged(boolean hasWindowFocus) {
3432        if (hasWindowFocus) {
3433            if (hasFocus()) {
3434                // If our window regained focus, and we have focus, then begin
3435                // drawing the cursor ring
3436                mDrawCursorRing = true;
3437                if (mNativeClass != 0) {
3438                    nativeRecordButtons(true, false, true);
3439                    if (inEditingMode()) {
3440                        mWebViewCore.sendMessage(EventHub.SET_ACTIVE, 1, 0);
3441                    }
3442                }
3443            } else {
3444                // If our window gained focus, but we do not have it, do not
3445                // draw the cursor ring.
3446                mDrawCursorRing = false;
3447                // We do not call nativeRecordButtons here because we assume
3448                // that when we lost focus, or window focus, it got called with
3449                // false for the first parameter
3450            }
3451        } else {
3452            if (getSettings().getBuiltInZoomControls() && !mZoomButtonsController.isVisible()) {
3453                /*
3454                 * The zoom controls come in their own window, so our window
3455                 * loses focus. Our policy is to not draw the cursor ring if
3456                 * our window is not focused, but this is an exception since
3457                 * the user can still navigate the web page with the zoom
3458                 * controls showing.
3459                 */
3460                // If our window has lost focus, stop drawing the cursor ring
3461                mDrawCursorRing = false;
3462            }
3463            mGotKeyDown = false;
3464            mShiftIsPressed = false;
3465            if (mNativeClass != 0) {
3466                nativeRecordButtons(false, false, true);
3467            }
3468            setFocusControllerInactive();
3469        }
3470        invalidate();
3471        super.onWindowFocusChanged(hasWindowFocus);
3472    }
3473
3474    /*
3475     * Pass a message to WebCore Thread, telling the WebCore::Page's
3476     * FocusController to be  "inactive" so that it will
3477     * not draw the blinking cursor.  It gets set to "active" to draw the cursor
3478     * in WebViewCore.cpp, when the WebCore thread receives key events/clicks.
3479     */
3480    /* package */ void setFocusControllerInactive() {
3481        // Do not need to also check whether mWebViewCore is null, because
3482        // mNativeClass is only set if mWebViewCore is non null
3483        if (mNativeClass == 0) return;
3484        mWebViewCore.sendMessage(EventHub.SET_ACTIVE, 0, 0);
3485    }
3486
3487    @Override
3488    protected void onFocusChanged(boolean focused, int direction,
3489            Rect previouslyFocusedRect) {
3490        if (DebugFlags.WEB_VIEW) {
3491            Log.v(LOGTAG, "MT focusChanged " + focused + ", " + direction);
3492        }
3493        if (focused) {
3494            // When we regain focus, if we have window focus, resume drawing
3495            // the cursor ring
3496            if (hasWindowFocus()) {
3497                mDrawCursorRing = true;
3498                if (mNativeClass != 0) {
3499                    nativeRecordButtons(true, false, true);
3500                }
3501            //} else {
3502                // The WebView has gained focus while we do not have
3503                // windowfocus.  When our window lost focus, we should have
3504                // called nativeRecordButtons(false...)
3505            }
3506        } else {
3507            // When we lost focus, unless focus went to the TextView (which is
3508            // true if we are in editing mode), stop drawing the cursor ring.
3509            if (!inEditingMode()) {
3510                mDrawCursorRing = false;
3511                if (mNativeClass != 0) {
3512                    nativeRecordButtons(false, false, true);
3513                }
3514                setFocusControllerInactive();
3515            }
3516            mGotKeyDown = false;
3517        }
3518
3519        super.onFocusChanged(focused, direction, previouslyFocusedRect);
3520    }
3521
3522    @Override
3523    protected void onSizeChanged(int w, int h, int ow, int oh) {
3524        super.onSizeChanged(w, h, ow, oh);
3525        // Center zooming to the center of the screen.
3526        if (mZoomScale == 0) { // unless we're already zooming
3527            mZoomCenterX = getViewWidth() * .5f;
3528            mZoomCenterY = getViewHeight() * .5f;
3529        }
3530
3531        // update mMinZoomScale if the minimum zoom scale is not fixed
3532        if (!mMinZoomScaleFixed) {
3533            mMinZoomScale = (float) getViewWidth()
3534                    / (mDrawHistory ? mHistoryPicture.getWidth()
3535                            : mZoomOverviewWidth);
3536        }
3537
3538        // we always force, in case our height changed, in which case we still
3539        // want to send the notification over to webkit
3540        setNewZoomScale(mActualScale, true);
3541    }
3542
3543    @Override
3544    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
3545        super.onScrollChanged(l, t, oldl, oldt);
3546
3547        sendOurVisibleRect();
3548    }
3549
3550
3551    @Override
3552    public boolean dispatchKeyEvent(KeyEvent event) {
3553        boolean dispatch = true;
3554
3555        if (!inEditingMode()) {
3556            if (event.getAction() == KeyEvent.ACTION_DOWN) {
3557                mGotKeyDown = true;
3558            } else {
3559                if (!mGotKeyDown) {
3560                    /*
3561                     * We got a key up for which we were not the recipient of
3562                     * the original key down. Don't give it to the view.
3563                     */
3564                    dispatch = false;
3565                }
3566                mGotKeyDown = false;
3567            }
3568        }
3569
3570        if (dispatch) {
3571            return super.dispatchKeyEvent(event);
3572        } else {
3573            // We didn't dispatch, so let something else handle the key
3574            return false;
3575        }
3576    }
3577
3578    // Here are the snap align logic:
3579    // 1. If it starts nearly horizontally or vertically, snap align;
3580    // 2. If there is a dramitic direction change, let it go;
3581    // 3. If there is a same direction back and forth, lock it.
3582
3583    // adjustable parameters
3584    private int mMinLockSnapReverseDistance;
3585    private static final float MAX_SLOPE_FOR_DIAG = 1.5f;
3586    private static final int MIN_BREAK_SNAP_CROSS_DISTANCE = 80;
3587
3588    @Override
3589    public boolean onTouchEvent(MotionEvent ev) {
3590        if (mNativeClass == 0 || !isClickable() || !isLongClickable()) {
3591            return false;
3592        }
3593
3594        if (DebugFlags.WEB_VIEW) {
3595            Log.v(LOGTAG, ev + " at " + ev.getEventTime() + " mTouchMode="
3596                    + mTouchMode);
3597        }
3598
3599        int action = ev.getAction();
3600        float x = ev.getX();
3601        float y = ev.getY();
3602        long eventTime = ev.getEventTime();
3603
3604        // Due to the touch screen edge effect, a touch closer to the edge
3605        // always snapped to the edge. As getViewWidth() can be different from
3606        // getWidth() due to the scrollbar, adjusting the point to match
3607        // getViewWidth(). Same applied to the height.
3608        if (x > getViewWidth() - 1) {
3609            x = getViewWidth() - 1;
3610        }
3611        if (y > getViewHeight() - 1) {
3612            y = getViewHeight() - 1;
3613        }
3614
3615        // pass the touch events from UI thread to WebCore thread
3616        if (mForwardTouchEvents && (action != MotionEvent.ACTION_MOVE
3617                || eventTime - mLastSentTouchTime > TOUCH_SENT_INTERVAL)) {
3618            WebViewCore.TouchEventData ted = new WebViewCore.TouchEventData();
3619            ted.mAction = action;
3620            ted.mX = viewToContentX((int) x + mScrollX);
3621            ted.mY = viewToContentY((int) y + mScrollY);
3622            mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
3623            mLastSentTouchTime = eventTime;
3624        }
3625
3626        int deltaX = (int) (mLastTouchX - x);
3627        int deltaY = (int) (mLastTouchY - y);
3628
3629        switch (action) {
3630            case MotionEvent.ACTION_DOWN: {
3631                if (!mScroller.isFinished()) {
3632                    // stop the current scroll animation, but if this is
3633                    // the start of a fling, allow it to add to the current
3634                    // fling's velocity
3635                    mScroller.abortAnimation();
3636                    mTouchMode = TOUCH_DRAG_START_MODE;
3637                    mPrivateHandler.removeMessages(RESUME_WEBCORE_UPDATE);
3638                } else if (mShiftIsPressed) {
3639                    mSelectX = mScrollX + (int) x;
3640                    mSelectY = mScrollY + (int) y;
3641                    mTouchMode = TOUCH_SELECT_MODE;
3642                    if (DebugFlags.WEB_VIEW) {
3643                        Log.v(LOGTAG, "select=" + mSelectX + "," + mSelectY);
3644                    }
3645                    nativeMoveSelection(viewToContentX(mSelectX),
3646                            viewToContentY(mSelectY), false);
3647                    mTouchSelection = mExtendSelection = true;
3648                } else if (mPrivateHandler.hasMessages(RELEASE_SINGLE_TAP)) {
3649                    mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP);
3650                    if (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare) {
3651                        mTouchMode = TOUCH_DOUBLE_TAP_MODE;
3652                    } else {
3653                        // commit the short press action for the previous tap
3654                        doShortPress();
3655                        // continue, mTouchMode should be still TOUCH_INIT_MODE
3656                    }
3657                } else {
3658                    mTouchMode = TOUCH_INIT_MODE;
3659                    mPreventDrag = mForwardTouchEvents;
3660                    mWebViewCore.sendMessage(
3661                            EventHub.UPDATE_FRAME_CACHE_IF_LOADING);
3662                    if (mLogEvent && eventTime - mLastTouchUpTime < 1000) {
3663                        EventLog.writeEvent(EVENT_LOG_DOUBLE_TAP_DURATION,
3664                                (eventTime - mLastTouchUpTime), eventTime);
3665                    }
3666                }
3667                // Trigger the link
3668                if (mTouchMode == TOUCH_INIT_MODE
3669                        || mTouchMode == TOUCH_DOUBLE_TAP_MODE) {
3670                    mPrivateHandler.sendMessageDelayed(mPrivateHandler
3671                            .obtainMessage(SWITCH_TO_SHORTPRESS), TAP_TIMEOUT);
3672                }
3673                // Remember where the motion event started
3674                mLastTouchX = x;
3675                mLastTouchY = y;
3676                mLastTouchTime = eventTime;
3677                mVelocityTracker = VelocityTracker.obtain();
3678                mSnapScrollMode = SNAP_NONE;
3679                break;
3680            }
3681            case MotionEvent.ACTION_MOVE: {
3682                if (mTouchMode == TOUCH_DONE_MODE) {
3683                    // no dragging during scroll zoom animation
3684                    break;
3685                }
3686                mVelocityTracker.addMovement(ev);
3687
3688                if (mTouchMode != TOUCH_DRAG_MODE) {
3689                    if (mTouchMode == TOUCH_SELECT_MODE) {
3690                        mSelectX = mScrollX + (int) x;
3691                        mSelectY = mScrollY + (int) y;
3692                        if (DebugFlags.WEB_VIEW) {
3693                            Log.v(LOGTAG, "xtend=" + mSelectX + "," + mSelectY);
3694                        }
3695                        nativeMoveSelection(viewToContentX(mSelectX),
3696                               viewToContentY(mSelectY), true);
3697                        invalidate();
3698                        break;
3699                    }
3700                    if (mPreventDrag || (deltaX * deltaX + deltaY * deltaY)
3701                            < mTouchSlopSquare) {
3702                        break;
3703                    }
3704
3705                    if (mTouchMode == TOUCH_SHORTPRESS_MODE
3706                            || mTouchMode == TOUCH_SHORTPRESS_START_MODE) {
3707                        mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
3708                    } else if (mTouchMode == TOUCH_INIT_MODE
3709                            || mTouchMode == TOUCH_DOUBLE_TAP_MODE) {
3710                        mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
3711                    }
3712
3713                    // if it starts nearly horizontal or vertical, enforce it
3714                    int ax = Math.abs(deltaX);
3715                    int ay = Math.abs(deltaY);
3716                    if (ax > MAX_SLOPE_FOR_DIAG * ay) {
3717                        mSnapScrollMode = SNAP_X;
3718                        mSnapPositive = deltaX > 0;
3719                    } else if (ay > MAX_SLOPE_FOR_DIAG * ax) {
3720                        mSnapScrollMode = SNAP_Y;
3721                        mSnapPositive = deltaY > 0;
3722                    }
3723
3724                    mTouchMode = TOUCH_DRAG_MODE;
3725                    WebViewCore.pauseUpdate(mWebViewCore);
3726                    if (!mDragFromTextInput) {
3727                        nativeHideCursor();
3728                    }
3729                    WebSettings settings = getSettings();
3730                    if (settings.supportZoom()
3731                            && settings.getBuiltInZoomControls()
3732                            && !mZoomButtonsController.isVisible()
3733                            && mMinZoomScale < mMaxZoomScale) {
3734                        mZoomButtonsController.setVisible(true);
3735                    }
3736                }
3737
3738                // do pan
3739                int newScrollX = pinLocX(mScrollX + deltaX);
3740                deltaX = newScrollX - mScrollX;
3741                int newScrollY = pinLocY(mScrollY + deltaY);
3742                deltaY = newScrollY - mScrollY;
3743                boolean done = false;
3744                if (deltaX == 0 && deltaY == 0) {
3745                    done = true;
3746                } else {
3747                    if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_Y) {
3748                        int ax = Math.abs(deltaX);
3749                        int ay = Math.abs(deltaY);
3750                        if (mSnapScrollMode == SNAP_X) {
3751                            // radical change means getting out of snap mode
3752                            if (ay > MAX_SLOPE_FOR_DIAG * ax
3753                                    && ay > MIN_BREAK_SNAP_CROSS_DISTANCE) {
3754                                mSnapScrollMode = SNAP_NONE;
3755                            }
3756                            // reverse direction means lock in the snap mode
3757                            if ((ax > MAX_SLOPE_FOR_DIAG * ay) &&
3758                                    ((mSnapPositive &&
3759                                    deltaX < -mMinLockSnapReverseDistance)
3760                                    || (!mSnapPositive &&
3761                                    deltaX > mMinLockSnapReverseDistance))) {
3762                                mSnapScrollMode = SNAP_X_LOCK;
3763                            }
3764                        } else {
3765                            // radical change means getting out of snap mode
3766                            if ((ax > MAX_SLOPE_FOR_DIAG * ay)
3767                                    && ax > MIN_BREAK_SNAP_CROSS_DISTANCE) {
3768                                mSnapScrollMode = SNAP_NONE;
3769                            }
3770                            // reverse direction means lock in the snap mode
3771                            if ((ay > MAX_SLOPE_FOR_DIAG * ax) &&
3772                                    ((mSnapPositive &&
3773                                    deltaY < -mMinLockSnapReverseDistance)
3774                                    || (!mSnapPositive &&
3775                                    deltaY > mMinLockSnapReverseDistance))) {
3776                                mSnapScrollMode = SNAP_Y_LOCK;
3777                            }
3778                        }
3779                    }
3780
3781                    if (mSnapScrollMode == SNAP_X
3782                            || mSnapScrollMode == SNAP_X_LOCK) {
3783                        scrollBy(deltaX, 0);
3784                        mLastTouchX = x;
3785                    } else if (mSnapScrollMode == SNAP_Y
3786                            || mSnapScrollMode == SNAP_Y_LOCK) {
3787                        scrollBy(0, deltaY);
3788                        mLastTouchY = y;
3789                    } else {
3790                        scrollBy(deltaX, deltaY);
3791                        mLastTouchX = x;
3792                        mLastTouchY = y;
3793                    }
3794                    mLastTouchTime = eventTime;
3795                    mUserScroll = true;
3796                }
3797
3798                if (!getSettings().getBuiltInZoomControls()) {
3799                    boolean showPlusMinus = mMinZoomScale < mMaxZoomScale;
3800                    if (mZoomControls != null && showPlusMinus) {
3801                        if (mZoomControls.getVisibility() == View.VISIBLE) {
3802                            mPrivateHandler.removeCallbacks(mZoomControlRunnable);
3803                        } else {
3804                            mZoomControls.show(showPlusMinus, false);
3805                        }
3806                        mPrivateHandler.postDelayed(mZoomControlRunnable,
3807                                ZOOM_CONTROLS_TIMEOUT);
3808                    }
3809                }
3810
3811                if (done) {
3812                    // return false to indicate that we can't pan out of the
3813                    // view space
3814                    return false;
3815                }
3816                break;
3817            }
3818            case MotionEvent.ACTION_UP: {
3819                mLastTouchUpTime = eventTime;
3820                switch (mTouchMode) {
3821                    case TOUCH_DOUBLE_TAP_MODE: // double tap
3822                        mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
3823                        mTouchMode = TOUCH_DONE_MODE;
3824                        doDoubleTap();
3825                        break;
3826                    case TOUCH_INIT_MODE: // tap
3827                        mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
3828                        if (!mPreventDrag) {
3829                            mPrivateHandler.sendMessageDelayed(
3830                                    mPrivateHandler.obtainMessage(
3831                                    RELEASE_SINGLE_TAP),
3832                                    ViewConfiguration.getDoubleTapTimeout());
3833                        }
3834                        break;
3835                    case TOUCH_SHORTPRESS_START_MODE:
3836                    case TOUCH_SHORTPRESS_MODE:
3837                        mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
3838                        mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
3839                        mTouchMode = TOUCH_DONE_MODE;
3840                        doShortPress();
3841                        break;
3842                    case TOUCH_SELECT_MODE:
3843                        commitCopy();
3844                        mTouchSelection = false;
3845                        break;
3846                    case TOUCH_DRAG_MODE:
3847                        // redraw in high-quality, as we're done dragging
3848                        invalidate();
3849                        // if the user waits a while w/o moving before the
3850                        // up, we don't want to do a fling
3851                        if (eventTime - mLastTouchTime <= MIN_FLING_TIME) {
3852                            mVelocityTracker.addMovement(ev);
3853                            doFling();
3854                            break;
3855                        }
3856                        mLastVelocity = 0;
3857                        WebViewCore.resumeUpdate(mWebViewCore);
3858                        break;
3859                    case TOUCH_DRAG_START_MODE:
3860                    case TOUCH_DONE_MODE:
3861                        // do nothing
3862                        break;
3863                }
3864                // we also use mVelocityTracker == null to tell us that we are
3865                // not "moving around", so we can take the slower/prettier
3866                // mode in the drawing code
3867                if (mVelocityTracker != null) {
3868                    mVelocityTracker.recycle();
3869                    mVelocityTracker = null;
3870                }
3871                break;
3872            }
3873            case MotionEvent.ACTION_CANCEL: {
3874                // we also use mVelocityTracker == null to tell us that we are
3875                // not "moving around", so we can take the slower/prettier
3876                // mode in the drawing code
3877                if (mVelocityTracker != null) {
3878                    mVelocityTracker.recycle();
3879                    mVelocityTracker = null;
3880                }
3881                if (mTouchMode == TOUCH_DRAG_MODE) {
3882                    WebViewCore.resumeUpdate(mWebViewCore);
3883                }
3884                mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
3885                mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
3886                mTouchMode = TOUCH_DONE_MODE;
3887                nativeHideCursor();
3888                break;
3889            }
3890        }
3891        return true;
3892    }
3893
3894    private long mTrackballFirstTime = 0;
3895    private long mTrackballLastTime = 0;
3896    private float mTrackballRemainsX = 0.0f;
3897    private float mTrackballRemainsY = 0.0f;
3898    private int mTrackballXMove = 0;
3899    private int mTrackballYMove = 0;
3900    private boolean mExtendSelection = false;
3901    private boolean mTouchSelection = false;
3902    private static final int TRACKBALL_KEY_TIMEOUT = 1000;
3903    private static final int TRACKBALL_TIMEOUT = 200;
3904    private static final int TRACKBALL_WAIT = 100;
3905    private static final int TRACKBALL_SCALE = 400;
3906    private static final int TRACKBALL_SCROLL_COUNT = 5;
3907    private static final int TRACKBALL_MOVE_COUNT = 10;
3908    private static final int TRACKBALL_MULTIPLIER = 3;
3909    private static final int SELECT_CURSOR_OFFSET = 16;
3910    private int mSelectX = 0;
3911    private int mSelectY = 0;
3912    private boolean mShiftIsPressed = false;
3913    private boolean mTrackballDown = false;
3914    private long mTrackballUpTime = 0;
3915    private long mLastCursorTime = 0;
3916    private Rect mLastCursorBounds;
3917
3918    // Set by default; BrowserActivity clears to interpret trackball data
3919    // directly for movement. Currently, the framework only passes
3920    // arrow key events, not trackball events, from one child to the next
3921    private boolean mMapTrackballToArrowKeys = true;
3922
3923    public void setMapTrackballToArrowKeys(boolean setMap) {
3924        mMapTrackballToArrowKeys = setMap;
3925    }
3926
3927    void resetTrackballTime() {
3928        mTrackballLastTime = 0;
3929    }
3930
3931    @Override
3932    public boolean onTrackballEvent(MotionEvent ev) {
3933        long time = ev.getEventTime();
3934        if ((ev.getMetaState() & KeyEvent.META_ALT_ON) != 0) {
3935            if (ev.getY() > 0) pageDown(true);
3936            if (ev.getY() < 0) pageUp(true);
3937            return true;
3938        }
3939        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
3940            mTrackballDown = true;
3941            if (mNativeClass == 0) {
3942                return false;
3943            }
3944            nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true);
3945            if (time - mLastCursorTime <= TRACKBALL_TIMEOUT
3946                    && !mLastCursorBounds.equals(nativeGetCursorRingBounds())) {
3947                nativeSelectBestAt(mLastCursorBounds);
3948            }
3949            if (DebugFlags.WEB_VIEW) {
3950                Log.v(LOGTAG, "onTrackballEvent down ev=" + ev
3951                        + " time=" + time
3952                        + " mLastCursorTime=" + mLastCursorTime);
3953            }
3954            if (isInTouchMode()) requestFocusFromTouch();
3955            return false; // let common code in onKeyDown at it
3956        }
3957        if (ev.getAction() == MotionEvent.ACTION_UP) {
3958            // LONG_PRESS_CENTER is set in common onKeyDown
3959            mPrivateHandler.removeMessages(LONG_PRESS_CENTER);
3960            mTrackballDown = false;
3961            mTrackballUpTime = time;
3962            if (mShiftIsPressed) {
3963                if (mExtendSelection) {
3964                    commitCopy();
3965                } else {
3966                    mExtendSelection = true;
3967                }
3968            }
3969            if (DebugFlags.WEB_VIEW) {
3970                Log.v(LOGTAG, "onTrackballEvent up ev=" + ev
3971                        + " time=" + time
3972                );
3973            }
3974            return false; // let common code in onKeyUp at it
3975        }
3976        if (mMapTrackballToArrowKeys && mShiftIsPressed == false) {
3977            if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent gmail quit");
3978            return false;
3979        }
3980        if (mTrackballDown) {
3981            if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent down quit");
3982            return true; // discard move if trackball is down
3983        }
3984        if (time - mTrackballUpTime < TRACKBALL_TIMEOUT) {
3985            if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent up timeout quit");
3986            return true;
3987        }
3988        // TODO: alternatively we can do panning as touch does
3989        switchOutDrawHistory();
3990        if (time - mTrackballLastTime > TRACKBALL_TIMEOUT) {
3991            if (DebugFlags.WEB_VIEW) {
3992                Log.v(LOGTAG, "onTrackballEvent time="
3993                        + time + " last=" + mTrackballLastTime);
3994            }
3995            mTrackballFirstTime = time;
3996            mTrackballXMove = mTrackballYMove = 0;
3997        }
3998        mTrackballLastTime = time;
3999        if (DebugFlags.WEB_VIEW) {
4000            Log.v(LOGTAG, "onTrackballEvent ev=" + ev + " time=" + time);
4001        }
4002        mTrackballRemainsX += ev.getX();
4003        mTrackballRemainsY += ev.getY();
4004        doTrackball(time);
4005        return true;
4006    }
4007
4008    void moveSelection(float xRate, float yRate) {
4009        if (mNativeClass == 0)
4010            return;
4011        int width = getViewWidth();
4012        int height = getViewHeight();
4013        mSelectX += scaleTrackballX(xRate, width);
4014        mSelectY += scaleTrackballY(yRate, height);
4015        int maxX = width + mScrollX;
4016        int maxY = height + mScrollY;
4017        mSelectX = Math.min(maxX, Math.max(mScrollX - SELECT_CURSOR_OFFSET
4018                , mSelectX));
4019        mSelectY = Math.min(maxY, Math.max(mScrollY - SELECT_CURSOR_OFFSET
4020                , mSelectY));
4021        if (DebugFlags.WEB_VIEW) {
4022            Log.v(LOGTAG, "moveSelection"
4023                    + " mSelectX=" + mSelectX
4024                    + " mSelectY=" + mSelectY
4025                    + " mScrollX=" + mScrollX
4026                    + " mScrollY=" + mScrollY
4027                    + " xRate=" + xRate
4028                    + " yRate=" + yRate
4029                    );
4030        }
4031        nativeMoveSelection(viewToContentX(mSelectX),
4032                viewToContentY(mSelectY), mExtendSelection);
4033        int scrollX = mSelectX < mScrollX ? -SELECT_CURSOR_OFFSET
4034                : mSelectX > maxX - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET
4035                : 0;
4036        int scrollY = mSelectY < mScrollY ? -SELECT_CURSOR_OFFSET
4037                : mSelectY > maxY - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET
4038                : 0;
4039        pinScrollBy(scrollX, scrollY, true, 0);
4040        Rect select = new Rect(mSelectX, mSelectY, mSelectX + 1, mSelectY + 1);
4041        requestRectangleOnScreen(select);
4042        invalidate();
4043   }
4044
4045    private int scaleTrackballX(float xRate, int width) {
4046        int xMove = (int) (xRate / TRACKBALL_SCALE * width);
4047        int nextXMove = xMove;
4048        if (xMove > 0) {
4049            if (xMove > mTrackballXMove) {
4050                xMove -= mTrackballXMove;
4051            }
4052        } else if (xMove < mTrackballXMove) {
4053            xMove -= mTrackballXMove;
4054        }
4055        mTrackballXMove = nextXMove;
4056        return xMove;
4057    }
4058
4059    private int scaleTrackballY(float yRate, int height) {
4060        int yMove = (int) (yRate / TRACKBALL_SCALE * height);
4061        int nextYMove = yMove;
4062        if (yMove > 0) {
4063            if (yMove > mTrackballYMove) {
4064                yMove -= mTrackballYMove;
4065            }
4066        } else if (yMove < mTrackballYMove) {
4067            yMove -= mTrackballYMove;
4068        }
4069        mTrackballYMove = nextYMove;
4070        return yMove;
4071    }
4072
4073    private int keyCodeToSoundsEffect(int keyCode) {
4074        switch(keyCode) {
4075            case KeyEvent.KEYCODE_DPAD_UP:
4076                return SoundEffectConstants.NAVIGATION_UP;
4077            case KeyEvent.KEYCODE_DPAD_RIGHT:
4078                return SoundEffectConstants.NAVIGATION_RIGHT;
4079            case KeyEvent.KEYCODE_DPAD_DOWN:
4080                return SoundEffectConstants.NAVIGATION_DOWN;
4081            case KeyEvent.KEYCODE_DPAD_LEFT:
4082                return SoundEffectConstants.NAVIGATION_LEFT;
4083        }
4084        throw new IllegalArgumentException("keyCode must be one of " +
4085                "{KEYCODE_DPAD_UP, KEYCODE_DPAD_RIGHT, KEYCODE_DPAD_DOWN, " +
4086                "KEYCODE_DPAD_LEFT}.");
4087    }
4088
4089    private void doTrackball(long time) {
4090        int elapsed = (int) (mTrackballLastTime - mTrackballFirstTime);
4091        if (elapsed == 0) {
4092            elapsed = TRACKBALL_TIMEOUT;
4093        }
4094        float xRate = mTrackballRemainsX * 1000 / elapsed;
4095        float yRate = mTrackballRemainsY * 1000 / elapsed;
4096        if (mShiftIsPressed) {
4097            moveSelection(xRate, yRate);
4098            mTrackballRemainsX = mTrackballRemainsY = 0;
4099            return;
4100        }
4101        float ax = Math.abs(xRate);
4102        float ay = Math.abs(yRate);
4103        float maxA = Math.max(ax, ay);
4104        if (DebugFlags.WEB_VIEW) {
4105            Log.v(LOGTAG, "doTrackball elapsed=" + elapsed
4106                    + " xRate=" + xRate
4107                    + " yRate=" + yRate
4108                    + " mTrackballRemainsX=" + mTrackballRemainsX
4109                    + " mTrackballRemainsY=" + mTrackballRemainsY);
4110        }
4111        int width = mContentWidth - getViewWidth();
4112        int height = mContentHeight - getViewHeight();
4113        if (width < 0) width = 0;
4114        if (height < 0) height = 0;
4115        ax = Math.abs(mTrackballRemainsX * TRACKBALL_MULTIPLIER);
4116        ay = Math.abs(mTrackballRemainsY * TRACKBALL_MULTIPLIER);
4117        maxA = Math.max(ax, ay);
4118        int count = Math.max(0, (int) maxA);
4119        int oldScrollX = mScrollX;
4120        int oldScrollY = mScrollY;
4121        if (count > 0) {
4122            int selectKeyCode = ax < ay ? mTrackballRemainsY < 0 ?
4123                    KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_DPAD_DOWN :
4124                    mTrackballRemainsX < 0 ? KeyEvent.KEYCODE_DPAD_LEFT :
4125                    KeyEvent.KEYCODE_DPAD_RIGHT;
4126            count = Math.min(count, TRACKBALL_MOVE_COUNT);
4127            if (DebugFlags.WEB_VIEW) {
4128                Log.v(LOGTAG, "doTrackball keyCode=" + selectKeyCode
4129                        + " count=" + count
4130                        + " mTrackballRemainsX=" + mTrackballRemainsX
4131                        + " mTrackballRemainsY=" + mTrackballRemainsY);
4132            }
4133            if (navHandledKey(selectKeyCode, count, false, time, false)) {
4134                playSoundEffect(keyCodeToSoundsEffect(selectKeyCode));
4135            }
4136            mTrackballRemainsX = mTrackballRemainsY = 0;
4137        }
4138        if (count >= TRACKBALL_SCROLL_COUNT) {
4139            int xMove = scaleTrackballX(xRate, width);
4140            int yMove = scaleTrackballY(yRate, height);
4141            if (DebugFlags.WEB_VIEW) {
4142                Log.v(LOGTAG, "doTrackball pinScrollBy"
4143                        + " count=" + count
4144                        + " xMove=" + xMove + " yMove=" + yMove
4145                        + " mScrollX-oldScrollX=" + (mScrollX-oldScrollX)
4146                        + " mScrollY-oldScrollY=" + (mScrollY-oldScrollY)
4147                        );
4148            }
4149            if (Math.abs(mScrollX - oldScrollX) > Math.abs(xMove)) {
4150                xMove = 0;
4151            }
4152            if (Math.abs(mScrollY - oldScrollY) > Math.abs(yMove)) {
4153                yMove = 0;
4154            }
4155            if (xMove != 0 || yMove != 0) {
4156                pinScrollBy(xMove, yMove, true, 0);
4157            }
4158            mUserScroll = true;
4159        }
4160    }
4161
4162    private int computeMaxScrollY() {
4163        int maxContentH = computeVerticalScrollRange() + getTitleHeight();
4164        return Math.max(maxContentH - getHeight(), getTitleHeight());
4165    }
4166
4167    public void flingScroll(int vx, int vy) {
4168        int maxX = Math.max(computeHorizontalScrollRange() - getViewWidth(), 0);
4169        int maxY = computeMaxScrollY();
4170
4171        mScroller.fling(mScrollX, mScrollY, vx, vy, 0, maxX, 0, maxY);
4172        invalidate();
4173    }
4174
4175    private void doFling() {
4176        if (mVelocityTracker == null) {
4177            return;
4178        }
4179        int maxX = Math.max(computeHorizontalScrollRange() - getViewWidth(), 0);
4180        int maxY = computeMaxScrollY();
4181
4182        mVelocityTracker.computeCurrentVelocity(1000, mMaximumFling);
4183        int vx = (int) mVelocityTracker.getXVelocity();
4184        int vy = (int) mVelocityTracker.getYVelocity();
4185
4186        if (mSnapScrollMode != SNAP_NONE) {
4187            if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_X_LOCK) {
4188                vy = 0;
4189            } else {
4190                vx = 0;
4191            }
4192        }
4193
4194        if (true /* EMG release: make our fling more like Maps' */) {
4195            // maps cuts their velocity in half
4196            vx = vx * 3 / 4;
4197            vy = vy * 3 / 4;
4198        }
4199        if ((maxX == 0 && vy == 0) || (maxY == 0 && vx == 0)) {
4200            WebViewCore.resumeUpdate(mWebViewCore);
4201            return;
4202        }
4203        float currentVelocity = mScroller.getCurrVelocity();
4204        if (mLastVelocity > 0 && currentVelocity > 0) {
4205            float deltaR = (float) (Math.abs(Math.atan2(mLastVelY, mLastVelX)
4206                    - Math.atan2(vy, vx)));
4207            final float circle = (float) (Math.PI) * 2.0f;
4208            if (deltaR > circle * 0.9f || deltaR < circle * 0.1f) {
4209                vx += currentVelocity * mLastVelX / mLastVelocity;
4210                vy += currentVelocity * mLastVelY / mLastVelocity;
4211                if (DebugFlags.WEB_VIEW) {
4212                    Log.v(LOGTAG, "doFling vx= " + vx + " vy=" + vy);
4213                }
4214            } else if (DebugFlags.WEB_VIEW) {
4215                Log.v(LOGTAG, "doFling missed " + deltaR / circle);
4216            }
4217        } else if (DebugFlags.WEB_VIEW) {
4218            Log.v(LOGTAG, "doFling start last=" + mLastVelocity
4219                    + " current=" + currentVelocity
4220                    + " vx=" + vx + " vy=" + vy
4221                    + " maxX=" + maxX + " maxY=" + maxY
4222                    + " mScrollX=" + mScrollX + " mScrollY=" + mScrollY);
4223        }
4224        mLastVelX = vx;
4225        mLastVelY = vy;
4226        mLastVelocity = (float) Math.hypot(vx, vy);
4227
4228        mScroller.fling(mScrollX, mScrollY, -vx, -vy, 0, maxX, 0, maxY);
4229        // TODO: duration is calculated based on velocity, if the range is
4230        // small, the animation will stop before duration is up. We may
4231        // want to calculate how long the animation is going to run to precisely
4232        // resume the webcore update.
4233        final int time = mScroller.getDuration();
4234        mPrivateHandler.sendEmptyMessageDelayed(RESUME_WEBCORE_UPDATE, time);
4235        invalidate();
4236    }
4237
4238    private boolean zoomWithPreview(float scale) {
4239        float oldScale = mActualScale;
4240        mInitialScrollX = mScrollX;
4241        mInitialScrollY = mScrollY;
4242
4243        // snap to DEFAULT_SCALE if it is close
4244        if (scale > (mDefaultScale - 0.05) && scale < (mDefaultScale + 0.05)) {
4245            scale = mDefaultScale;
4246        }
4247
4248        setNewZoomScale(scale, false);
4249
4250        if (oldScale != mActualScale) {
4251            // use mZoomPickerScale to see zoom preview first
4252            mZoomStart = SystemClock.uptimeMillis();
4253            mInvInitialZoomScale = 1.0f / oldScale;
4254            mInvFinalZoomScale = 1.0f / mActualScale;
4255            mZoomScale = mActualScale;
4256            if (!mInZoomOverview) {
4257                mLastScale = scale;
4258            }
4259            invalidate();
4260            return true;
4261        } else {
4262            return false;
4263        }
4264    }
4265
4266    /**
4267     * Returns a view containing zoom controls i.e. +/- buttons. The caller is
4268     * in charge of installing this view to the view hierarchy. This view will
4269     * become visible when the user starts scrolling via touch and fade away if
4270     * the user does not interact with it.
4271     * <p/>
4272     * API version 3 introduces a built-in zoom mechanism that is shown
4273     * automatically by the MapView. This is the preferred approach for
4274     * showing the zoom UI.
4275     *
4276     * @deprecated The built-in zoom mechanism is preferred, see
4277     *             {@link WebSettings#setBuiltInZoomControls(boolean)}.
4278     */
4279    @Deprecated
4280    public View getZoomControls() {
4281        if (!getSettings().supportZoom()) {
4282            Log.w(LOGTAG, "This WebView doesn't support zoom.");
4283            return null;
4284        }
4285        if (mZoomControls == null) {
4286            mZoomControls = createZoomControls();
4287
4288            /*
4289             * need to be set to VISIBLE first so that getMeasuredHeight() in
4290             * {@link #onSizeChanged()} can return the measured value for proper
4291             * layout.
4292             */
4293            mZoomControls.setVisibility(View.VISIBLE);
4294            mZoomControlRunnable = new Runnable() {
4295                public void run() {
4296
4297                    /* Don't dismiss the controls if the user has
4298                     * focus on them. Wait and check again later.
4299                     */
4300                    if (!mZoomControls.hasFocus()) {
4301                        mZoomControls.hide();
4302                    } else {
4303                        mPrivateHandler.removeCallbacks(mZoomControlRunnable);
4304                        mPrivateHandler.postDelayed(mZoomControlRunnable,
4305                                ZOOM_CONTROLS_TIMEOUT);
4306                    }
4307                }
4308            };
4309        }
4310        return mZoomControls;
4311    }
4312
4313    private ExtendedZoomControls createZoomControls() {
4314        ExtendedZoomControls zoomControls = new ExtendedZoomControls(mContext
4315            , null);
4316        zoomControls.setOnZoomInClickListener(new OnClickListener() {
4317            public void onClick(View v) {
4318                // reset time out
4319                mPrivateHandler.removeCallbacks(mZoomControlRunnable);
4320                mPrivateHandler.postDelayed(mZoomControlRunnable,
4321                        ZOOM_CONTROLS_TIMEOUT);
4322                zoomIn();
4323            }
4324        });
4325        zoomControls.setOnZoomOutClickListener(new OnClickListener() {
4326            public void onClick(View v) {
4327                // reset time out
4328                mPrivateHandler.removeCallbacks(mZoomControlRunnable);
4329                mPrivateHandler.postDelayed(mZoomControlRunnable,
4330                        ZOOM_CONTROLS_TIMEOUT);
4331                zoomOut();
4332            }
4333        });
4334        return zoomControls;
4335    }
4336
4337    /**
4338     * Gets the {@link ZoomButtonsController} which can be used to add
4339     * additional buttons to the zoom controls window.
4340     *
4341     * @return The instance of {@link ZoomButtonsController} used by this class,
4342     *         or null if it is unavailable.
4343     * @hide
4344     */
4345    public ZoomButtonsController getZoomButtonsController() {
4346        return mZoomButtonsController;
4347    }
4348
4349    /**
4350     * Perform zoom in in the webview
4351     * @return TRUE if zoom in succeeds. FALSE if no zoom changes.
4352     */
4353    public boolean zoomIn() {
4354        // TODO: alternatively we can disallow this during draw history mode
4355        switchOutDrawHistory();
4356        // Center zooming to the center of the screen.
4357        if (mInZoomOverview) {
4358            // if in overview mode, bring it back to normal mode
4359            mLastTouchX = getViewWidth() * .5f;
4360            mLastTouchY = getViewHeight() * .5f;
4361            doDoubleTap();
4362            return true;
4363        } else {
4364            mZoomCenterX = getViewWidth() * .5f;
4365            mZoomCenterY = getViewHeight() * .5f;
4366            return zoomWithPreview(mActualScale * 1.25f);
4367        }
4368    }
4369
4370    /**
4371     * Perform zoom out in the webview
4372     * @return TRUE if zoom out succeeds. FALSE if no zoom changes.
4373     */
4374    public boolean zoomOut() {
4375        // TODO: alternatively we can disallow this during draw history mode
4376        switchOutDrawHistory();
4377        float scale = mActualScale * 0.8f;
4378        if (scale < (mMinZoomScale + 0.1f)
4379                && mWebViewCore.getSettings().getUseWideViewPort()) {
4380            // when zoom out to min scale, switch to overview mode
4381            doDoubleTap();
4382            return true;
4383        } else {
4384            // Center zooming to the center of the screen.
4385            mZoomCenterX = getViewWidth() * .5f;
4386            mZoomCenterY = getViewHeight() * .5f;
4387            return zoomWithPreview(scale);
4388        }
4389    }
4390
4391    private void updateSelection() {
4392        if (mNativeClass == 0) {
4393            return;
4394        }
4395        // mLastTouchX and mLastTouchY are the point in the current viewport
4396        int contentX = viewToContentX((int) mLastTouchX + mScrollX);
4397        int contentY = viewToContentY((int) mLastTouchY + mScrollY);
4398        Rect rect = new Rect(contentX - mNavSlop, contentY - mNavSlop,
4399                contentX + mNavSlop, contentY + mNavSlop);
4400        nativeSelectBestAt(rect);
4401    }
4402
4403    /**
4404     * Scroll the focused text field/area to match the WebTextView
4405     * @param x New x position of the WebTextView in view coordinates
4406     * @param y New y position of the WebTextView in view coordinates
4407     */
4408    /*package*/ void scrollFocusedTextInput(int x, int y) {
4409        if (!inEditingMode() || mWebViewCore == null) {
4410            return;
4411        }
4412        mWebViewCore.sendMessage(EventHub.SCROLL_TEXT_INPUT, viewToContentX(x),
4413                viewToContentY(y));
4414    }
4415
4416    /**
4417     * Set our starting point and time for a drag from the WebTextView.
4418     */
4419    /*package*/ void initiateTextFieldDrag(float x, float y, long eventTime) {
4420        if (!inEditingMode()) {
4421            return;
4422        }
4423        mLastTouchX = x + (float) (mWebTextView.getLeft() - mScrollX);
4424        mLastTouchY = y + (float) (mWebTextView.getTop() - mScrollY);
4425        mLastTouchTime = eventTime;
4426        if (!mScroller.isFinished()) {
4427            abortAnimation();
4428            mPrivateHandler.removeMessages(RESUME_WEBCORE_UPDATE);
4429        }
4430        mSnapScrollMode = SNAP_NONE;
4431        mVelocityTracker = VelocityTracker.obtain();
4432        mTouchMode = TOUCH_DRAG_START_MODE;
4433    }
4434
4435    /**
4436     * Given a motion event from the WebTextView, set its location to our
4437     * coordinates, and handle the event.
4438     */
4439    /*package*/ boolean textFieldDrag(MotionEvent event) {
4440        if (!inEditingMode()) {
4441            return false;
4442        }
4443        mDragFromTextInput = true;
4444        event.offsetLocation((float) (mWebTextView.getLeft() - mScrollX),
4445                (float) (mWebTextView.getTop() - mScrollY));
4446        boolean result = onTouchEvent(event);
4447        mDragFromTextInput = false;
4448        return result;
4449    }
4450
4451    /**
4452     * Do a touch up from a WebTextView.  This will be handled by webkit to
4453     * change the selection.
4454     * @param event MotionEvent in the WebTextView's coordinates.
4455     */
4456    /*package*/ void touchUpOnTextField(MotionEvent event) {
4457        if (!inEditingMode()) {
4458            return;
4459        }
4460        int x = viewToContentX((int) event.getX() + mWebTextView.getLeft());
4461        int y = viewToContentY((int) event.getY() + mWebTextView.getTop());
4462        // In case the soft keyboard has been dismissed, bring it back up.
4463        InputMethodManager.getInstance(getContext()).showSoftInput(mWebTextView,
4464                0);
4465        if (nativeFocusNodePointer() != nativeCursorNodePointer()) {
4466            nativeMotionUp(x, y, mNavSlop);
4467        }
4468        nativeTextInputMotionUp(x, y);
4469    }
4470
4471    /*package*/ void shortPressOnTextField() {
4472        if (inEditingMode()) {
4473            View v = mWebTextView;
4474            int x = viewToContentX((v.getLeft() + v.getRight()) >> 1);
4475            int y = viewToContentY((v.getTop() + v.getBottom()) >> 1);
4476            nativeTextInputMotionUp(x, y);
4477        }
4478    }
4479
4480    private void doShortPress() {
4481        if (mNativeClass == 0) {
4482            return;
4483        }
4484        switchOutDrawHistory();
4485        // mLastTouchX and mLastTouchY are the point in the current viewport
4486        int contentX = viewToContentX((int) mLastTouchX + mScrollX);
4487        int contentY = viewToContentY((int) mLastTouchY + mScrollY);
4488        if (nativeMotionUp(contentX, contentY, mNavSlop)) {
4489            if (mLogEvent) {
4490                Checkin.updateStats(mContext.getContentResolver(),
4491                        Checkin.Stats.Tag.BROWSER_SNAP_CENTER, 1, 0.0);
4492            }
4493        }
4494        if (nativeHasCursorNode() && !nativeCursorIsTextInput()) {
4495            playSoundEffect(SoundEffectConstants.CLICK);
4496        }
4497    }
4498
4499    private void doDoubleTap() {
4500        if (mWebViewCore.getSettings().getUseWideViewPort() == false) {
4501            return;
4502        }
4503        mZoomCenterX = mLastTouchX;
4504        mZoomCenterY = mLastTouchY;
4505        mInZoomOverview = !mInZoomOverview;
4506        // remove the zoom control after double tap
4507        if (getSettings().getBuiltInZoomControls()) {
4508            if (mZoomButtonsController.isVisible()) {
4509                mZoomButtonsController.setVisible(false);
4510            }
4511        } else {
4512            if (mZoomControlRunnable != null) {
4513                mPrivateHandler.removeCallbacks(mZoomControlRunnable);
4514            }
4515            if (mZoomControls != null) {
4516                mZoomControls.hide();
4517            }
4518        }
4519        if (mInZoomOverview) {
4520            zoomWithPreview((float) getViewWidth() / mZoomOverviewWidth);
4521        } else {
4522            // mLastTouchX and mLastTouchY are the point in the current viewport
4523            int contentX = viewToContentX((int) mLastTouchX + mScrollX);
4524            int contentY = viewToContentY((int) mLastTouchY + mScrollY);
4525            int left = nativeGetBlockLeftEdge(contentX, contentY, mActualScale);
4526            if (left != NO_LEFTEDGE) {
4527                // add a 5pt padding to the left edge. Re-calculate the zoom
4528                // center so that the new scroll x will be on the left edge.
4529                mZoomCenterX = left < 5 ? 0 : (left - 5) * mLastScale
4530                        * mActualScale / (mLastScale - mActualScale);
4531            }
4532            zoomWithPreview(mLastScale);
4533        }
4534    }
4535
4536    // Called by JNI to handle a touch on a node representing an email address,
4537    // address, or phone number
4538    private void overrideLoading(String url) {
4539        mCallbackProxy.uiOverrideUrlLoading(url);
4540    }
4541
4542    // called by JNI
4543    private void sendPluginState(int state) {
4544        WebViewCore.PluginStateData psd = new WebViewCore.PluginStateData();
4545        psd.mFrame = nativeCursorFramePointer();
4546        psd.mNode = nativeCursorNodePointer();
4547        psd.mState = state;
4548        mWebViewCore.sendMessage(EventHub.PLUGIN_STATE, psd);
4549    }
4550
4551    @Override
4552    public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
4553        boolean result = false;
4554        if (inEditingMode()) {
4555            result = mWebTextView.requestFocus(direction,
4556                    previouslyFocusedRect);
4557        } else {
4558            result = super.requestFocus(direction, previouslyFocusedRect);
4559            if (mWebViewCore.getSettings().getNeedInitialFocus()) {
4560                // For cases such as GMail, where we gain focus from a direction,
4561                // we want to move to the first available link.
4562                // FIXME: If there are no visible links, we may not want to
4563                int fakeKeyDirection = 0;
4564                switch(direction) {
4565                    case View.FOCUS_UP:
4566                        fakeKeyDirection = KeyEvent.KEYCODE_DPAD_UP;
4567                        break;
4568                    case View.FOCUS_DOWN:
4569                        fakeKeyDirection = KeyEvent.KEYCODE_DPAD_DOWN;
4570                        break;
4571                    case View.FOCUS_LEFT:
4572                        fakeKeyDirection = KeyEvent.KEYCODE_DPAD_LEFT;
4573                        break;
4574                    case View.FOCUS_RIGHT:
4575                        fakeKeyDirection = KeyEvent.KEYCODE_DPAD_RIGHT;
4576                        break;
4577                    default:
4578                        return result;
4579                }
4580                if (mNativeClass != 0 && !nativeHasCursorNode()) {
4581                    navHandledKey(fakeKeyDirection, 1, true, 0, true);
4582                }
4583            }
4584        }
4585        return result;
4586    }
4587
4588    @Override
4589    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
4590        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
4591
4592        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
4593        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
4594        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
4595        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
4596
4597        int measuredHeight = heightSize;
4598        int measuredWidth = widthSize;
4599
4600        // Grab the content size from WebViewCore.
4601        int contentHeight = contentToViewDimension(mContentHeight);
4602        int contentWidth = contentToViewDimension(mContentWidth);
4603
4604//        Log.d(LOGTAG, "------- measure " + heightMode);
4605
4606        if (heightMode != MeasureSpec.EXACTLY) {
4607            mHeightCanMeasure = true;
4608            measuredHeight = contentHeight;
4609            if (heightMode == MeasureSpec.AT_MOST) {
4610                // If we are larger than the AT_MOST height, then our height can
4611                // no longer be measured and we should scroll internally.
4612                if (measuredHeight > heightSize) {
4613                    measuredHeight = heightSize;
4614                    mHeightCanMeasure = false;
4615                }
4616            }
4617        } else {
4618            mHeightCanMeasure = false;
4619        }
4620        if (mNativeClass != 0) {
4621            nativeSetHeightCanMeasure(mHeightCanMeasure);
4622        }
4623        // For the width, always use the given size unless unspecified.
4624        if (widthMode == MeasureSpec.UNSPECIFIED) {
4625            mWidthCanMeasure = true;
4626            measuredWidth = contentWidth;
4627        } else {
4628            mWidthCanMeasure = false;
4629        }
4630
4631        synchronized (this) {
4632            setMeasuredDimension(measuredWidth, measuredHeight);
4633        }
4634    }
4635
4636    @Override
4637    public boolean requestChildRectangleOnScreen(View child,
4638                                                 Rect rect,
4639                                                 boolean immediate) {
4640        rect.offset(child.getLeft() - child.getScrollX(),
4641                child.getTop() - child.getScrollY());
4642
4643        int height = getHeight() - getHorizontalScrollbarHeight();
4644        int screenTop = mScrollY;
4645        int screenBottom = screenTop + height;
4646
4647        int scrollYDelta = 0;
4648
4649        if (rect.bottom > screenBottom) {
4650            int oneThirdOfScreenHeight = height / 3;
4651            if (rect.height() > 2 * oneThirdOfScreenHeight) {
4652                // If the rectangle is too tall to fit in the bottom two thirds
4653                // of the screen, place it at the top.
4654                scrollYDelta = rect.top - screenTop;
4655            } else {
4656                // If the rectangle will still fit on screen, we want its
4657                // top to be in the top third of the screen.
4658                scrollYDelta = rect.top - (screenTop + oneThirdOfScreenHeight);
4659            }
4660        } else if (rect.top < screenTop) {
4661            scrollYDelta = rect.top - screenTop;
4662        }
4663
4664        int width = getWidth() - getVerticalScrollbarWidth();
4665        int screenLeft = mScrollX;
4666        int screenRight = screenLeft + width;
4667
4668        int scrollXDelta = 0;
4669
4670        if (rect.right > screenRight && rect.left > screenLeft) {
4671            if (rect.width() > width) {
4672                scrollXDelta += (rect.left - screenLeft);
4673            } else {
4674                scrollXDelta += (rect.right - screenRight);
4675            }
4676        } else if (rect.left < screenLeft) {
4677            scrollXDelta -= (screenLeft - rect.left);
4678        }
4679
4680        if ((scrollYDelta | scrollXDelta) != 0) {
4681            return pinScrollBy(scrollXDelta, scrollYDelta, !immediate, 0);
4682        }
4683
4684        return false;
4685    }
4686
4687    /* package */ void replaceTextfieldText(int oldStart, int oldEnd,
4688            String replace, int newStart, int newEnd) {
4689        WebViewCore.ReplaceTextData arg = new WebViewCore.ReplaceTextData();
4690        arg.mReplace = replace;
4691        arg.mNewStart = newStart;
4692        arg.mNewEnd = newEnd;
4693        mTextGeneration++;
4694        arg.mTextGeneration = mTextGeneration;
4695        mWebViewCore.sendMessage(EventHub.REPLACE_TEXT, oldStart, oldEnd, arg);
4696    }
4697
4698    /* package */ void passToJavaScript(String currentText, KeyEvent event) {
4699        if (nativeCursorWantsKeyEvents() && !nativeCursorMatchesFocus()) {
4700            mWebViewCore.sendMessage(EventHub.CLICK);
4701        }
4702        WebViewCore.JSKeyData arg = new WebViewCore.JSKeyData();
4703        arg.mEvent = event;
4704        arg.mCurrentText = currentText;
4705        // Increase our text generation number, and pass it to webcore thread
4706        mTextGeneration++;
4707        mWebViewCore.sendMessage(EventHub.PASS_TO_JS, mTextGeneration, 0, arg);
4708        // WebKit's document state is not saved until about to leave the page.
4709        // To make sure the host application, like Browser, has the up to date
4710        // document state when it goes to background, we force to save the
4711        // document state.
4712        mWebViewCore.removeMessages(EventHub.SAVE_DOCUMENT_STATE);
4713        mWebViewCore.sendMessageDelayed(EventHub.SAVE_DOCUMENT_STATE,
4714                cursorData(), 1000);
4715    }
4716
4717    /* package */ WebViewCore getWebViewCore() {
4718        return mWebViewCore;
4719    }
4720
4721    //-------------------------------------------------------------------------
4722    // Methods can be called from a separate thread, like WebViewCore
4723    // If it needs to call the View system, it has to send message.
4724    //-------------------------------------------------------------------------
4725
4726    /**
4727     * General handler to receive message coming from webkit thread
4728     */
4729    class PrivateHandler extends Handler {
4730        @Override
4731        public void handleMessage(Message msg) {
4732            if (DebugFlags.WEB_VIEW) {
4733                Log.v(LOGTAG, msg.what < REMEMBER_PASSWORD || msg.what
4734                        > INVAL_RECT_MSG_ID ? Integer.toString(msg.what)
4735                        : HandlerDebugString[msg.what - REMEMBER_PASSWORD]);
4736            }
4737            switch (msg.what) {
4738                case REMEMBER_PASSWORD: {
4739                    mDatabase.setUsernamePassword(
4740                            msg.getData().getString("host"),
4741                            msg.getData().getString("username"),
4742                            msg.getData().getString("password"));
4743                    ((Message) msg.obj).sendToTarget();
4744                    break;
4745                }
4746                case NEVER_REMEMBER_PASSWORD: {
4747                    mDatabase.setUsernamePassword(
4748                            msg.getData().getString("host"), null, null);
4749                    ((Message) msg.obj).sendToTarget();
4750                    break;
4751                }
4752                case SWITCH_TO_SHORTPRESS: {
4753                    if (mTouchMode == TOUCH_INIT_MODE) {
4754                        mTouchMode = TOUCH_SHORTPRESS_START_MODE;
4755                        updateSelection();
4756                    } else if (mTouchMode == TOUCH_DOUBLE_TAP_MODE) {
4757                        mTouchMode = TOUCH_DONE_MODE;
4758                    }
4759                    break;
4760                }
4761                case SWITCH_TO_LONGPRESS: {
4762                    if (!mPreventDrag) {
4763                        mTouchMode = TOUCH_DONE_MODE;
4764                        performLongClick();
4765                        rebuildWebTextView();
4766                    }
4767                    break;
4768                }
4769                case RELEASE_SINGLE_TAP: {
4770                    if (!mPreventDrag) {
4771                        mTouchMode = TOUCH_DONE_MODE;
4772                        doShortPress();
4773                    }
4774                    break;
4775                }
4776                case SCROLL_BY_MSG_ID:
4777                    setContentScrollBy(msg.arg1, msg.arg2, (Boolean) msg.obj);
4778                    break;
4779                case SYNC_SCROLL_TO_MSG_ID:
4780                    if (mUserScroll) {
4781                        // if user has scrolled explicitly, don't sync the
4782                        // scroll position any more
4783                        mUserScroll = false;
4784                        break;
4785                    }
4786                    // fall through
4787                case SCROLL_TO_MSG_ID:
4788                    if (setContentScrollTo(msg.arg1, msg.arg2)) {
4789                        // if we can't scroll to the exact position due to pin,
4790                        // send a message to WebCore to re-scroll when we get a
4791                        // new picture
4792                        mUserScroll = false;
4793                        mWebViewCore.sendMessage(EventHub.SYNC_SCROLL,
4794                                msg.arg1, msg.arg2);
4795                    }
4796                    break;
4797                case SPAWN_SCROLL_TO_MSG_ID:
4798                    spawnContentScrollTo(msg.arg1, msg.arg2);
4799                    break;
4800                case NEW_PICTURE_MSG_ID: {
4801                    WebSettings settings = mWebViewCore.getSettings();
4802                    // called for new content
4803                    final int viewWidth = getViewWidth();
4804                    final WebViewCore.DrawData draw =
4805                            (WebViewCore.DrawData) msg.obj;
4806                    final Point viewSize = draw.mViewPoint;
4807                    boolean useWideViewport = settings.getUseWideViewPort();
4808                    WebViewCore.RestoreState restoreState = draw.mRestoreState;
4809                    if (restoreState != null) {
4810                        mInZoomOverview = false;
4811                        mLastScale = restoreState.mTextWrapScale;
4812                        if (restoreState.mMinScale == 0) {
4813                            if (restoreState.mMobileSite) {
4814                                if (draw.mMinPrefWidth > draw.mViewPoint.x) {
4815                                    mMinZoomScale = (float) viewWidth
4816                                            / draw.mMinPrefWidth;
4817                                    mMinZoomScaleFixed = false;
4818                                } else {
4819                                    mMinZoomScale = mDefaultScale;
4820                                    mMinZoomScaleFixed = true;
4821                                }
4822                            } else {
4823                                mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE;
4824                                mMinZoomScaleFixed = false;
4825                            }
4826                        } else {
4827                            mMinZoomScale = restoreState.mMinScale;
4828                            mMinZoomScaleFixed = true;
4829                        }
4830                        if (restoreState.mMaxScale == 0) {
4831                            mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE;
4832                        } else {
4833                            mMaxZoomScale = restoreState.mMaxScale;
4834                        }
4835                        setNewZoomScale(mLastScale, false);
4836                        if (getTitleHeight() != 0 && restoreState.mScrollX == 0
4837                                && restoreState.mScrollY == 0) {
4838                            // If there is a title bar, and the page is being
4839                            // restored to (0,0), do not scroll the title bar
4840                            // off the page.
4841                            abortAnimation();
4842                            scrollTo(0,0);
4843                        } else {
4844                            setContentScrollTo(restoreState.mScrollX,
4845                                    restoreState.mScrollY);
4846                        }
4847                        if (useWideViewport
4848                                && settings.getLoadWithOverviewMode()) {
4849                            if (restoreState.mViewScale == 0
4850                                    || (restoreState.mMobileSite
4851                                            && mMinZoomScale < mDefaultScale)) {
4852                                mInZoomOverview = true;
4853                            }
4854                        }
4855                        // As we are on a new page, remove the WebTextView. This
4856                        // is necessary for page loads driven by webkit, and in
4857                        // particular when the user was on a password field, so
4858                        // the WebTextView was visible.
4859                        clearTextEntry();
4860                    }
4861                    // We update the layout (i.e. request a layout from the
4862                    // view system) if the last view size that we sent to
4863                    // WebCore matches the view size of the picture we just
4864                    // received in the fixed dimension.
4865                    final boolean updateLayout = viewSize.x == mLastWidthSent
4866                            && viewSize.y == mLastHeightSent;
4867                    recordNewContentSize(draw.mWidthHeight.x,
4868                            draw.mWidthHeight.y, updateLayout);
4869                    if (DebugFlags.WEB_VIEW) {
4870                        Rect b = draw.mInvalRegion.getBounds();
4871                        Log.v(LOGTAG, "NEW_PICTURE_MSG_ID {" +
4872                                b.left+","+b.top+","+b.right+","+b.bottom+"}");
4873                    }
4874                    invalidateContentRect(draw.mInvalRegion.getBounds());
4875                    if (mPictureListener != null) {
4876                        mPictureListener.onNewPicture(WebView.this, capturePicture());
4877                    }
4878                    if (useWideViewport) {
4879                        mZoomOverviewWidth = Math.max(draw.mMinPrefWidth,
4880                                draw.mViewPoint.x);
4881                    }
4882                    if (!mMinZoomScaleFixed) {
4883                        mMinZoomScale = (float) viewWidth / mZoomOverviewWidth;
4884                    }
4885                    if (!mDrawHistory && mInZoomOverview) {
4886                        // fit the content width to the current view. Ignore
4887                        // the rounding error case.
4888                        if (Math.abs((viewWidth * mInvActualScale)
4889                                - mZoomOverviewWidth) > 1) {
4890                            setNewZoomScale((float) viewWidth
4891                                    / mZoomOverviewWidth, false);
4892                        }
4893                    }
4894                    break;
4895                }
4896                case WEBCORE_INITIALIZED_MSG_ID:
4897                    // nativeCreate sets mNativeClass to a non-zero value
4898                    nativeCreate(msg.arg1);
4899                    break;
4900                case UPDATE_TEXTFIELD_TEXT_MSG_ID:
4901                    // Make sure that the textfield is currently focused
4902                    // and representing the same node as the pointer.
4903                    if (inEditingMode() &&
4904                            mWebTextView.isSameTextField(msg.arg1)) {
4905                        if (msg.getData().getBoolean("password")) {
4906                            Spannable text = (Spannable) mWebTextView.getText();
4907                            int start = Selection.getSelectionStart(text);
4908                            int end = Selection.getSelectionEnd(text);
4909                            mWebTextView.setInPassword(true);
4910                            // Restore the selection, which may have been
4911                            // ruined by setInPassword.
4912                            Spannable pword =
4913                                    (Spannable) mWebTextView.getText();
4914                            Selection.setSelection(pword, start, end);
4915                        // If the text entry has created more events, ignore
4916                        // this one.
4917                        } else if (msg.arg2 == mTextGeneration) {
4918                            mWebTextView.setTextAndKeepSelection(
4919                                    (String) msg.obj);
4920                        }
4921                    }
4922                    break;
4923                case UPDATE_TEXT_SELECTION_MSG_ID:
4924                    if (inEditingMode()
4925                            && mWebTextView.isSameTextField(msg.arg1)
4926                            && msg.arg2 == mTextGeneration) {
4927                        WebViewCore.TextSelectionData tData
4928                                = (WebViewCore.TextSelectionData) msg.obj;
4929                        mWebTextView.setSelectionFromWebKit(tData.mStart,
4930                                tData.mEnd);
4931                    }
4932                    break;
4933                case MOVE_OUT_OF_PLUGIN:
4934                    if (nativePluginEatsNavKey()) {
4935                        navHandledKey(msg.arg1, 1, false, 0, true);
4936                    }
4937                    break;
4938                case UPDATE_TEXT_ENTRY_MSG_ID:
4939                    // this is sent after finishing resize in WebViewCore. Make
4940                    // sure the text edit box is still on the  screen.
4941                    if (inEditingMode() && nativeCursorIsTextInput()) {
4942                        mWebTextView.bringIntoView();
4943                        rebuildWebTextView();
4944                    }
4945                    break;
4946                case CLEAR_TEXT_ENTRY:
4947                    clearTextEntry();
4948                    break;
4949                case INVAL_RECT_MSG_ID: {
4950                    Rect r = (Rect)msg.obj;
4951                    if (r == null) {
4952                        invalidate();
4953                    } else {
4954                        // we need to scale r from content into view coords,
4955                        // which viewInvalidate() does for us
4956                        viewInvalidate(r.left, r.top, r.right, r.bottom);
4957                    }
4958                    break;
4959                }
4960                case REQUEST_FORM_DATA:
4961                    AutoCompleteAdapter adapter = (AutoCompleteAdapter) msg.obj;
4962                    if (mWebTextView.isSameTextField(msg.arg1)) {
4963                        mWebTextView.setAdapterCustom(adapter);
4964                    }
4965                    break;
4966                case UPDATE_CLIPBOARD:
4967                    String str = (String) msg.obj;
4968                    if (DebugFlags.WEB_VIEW) {
4969                        Log.v(LOGTAG, "UPDATE_CLIPBOARD " + str);
4970                    }
4971                    try {
4972                        IClipboard clip = IClipboard.Stub.asInterface(
4973                                ServiceManager.getService("clipboard"));
4974                                clip.setClipboardText(str);
4975                    } catch (android.os.RemoteException e) {
4976                        Log.e(LOGTAG, "Clipboard failed", e);
4977                    }
4978                    break;
4979                case RESUME_WEBCORE_UPDATE:
4980                    WebViewCore.resumeUpdate(mWebViewCore);
4981                    break;
4982
4983                case LONG_PRESS_CENTER:
4984                    // as this is shared by keydown and trackballdown, reset all
4985                    // the states
4986                    mGotCenterDown = false;
4987                    mTrackballDown = false;
4988                    // LONG_PRESS_CENTER is sent as a delayed message. If we
4989                    // switch to windows overview, the WebView will be
4990                    // temporarily removed from the view system. In that case,
4991                    // do nothing.
4992                    if (getParent() != null) {
4993                        performLongClick();
4994                    }
4995                    break;
4996
4997                case WEBCORE_NEED_TOUCH_EVENTS:
4998                    mForwardTouchEvents = (msg.arg1 != 0);
4999                    break;
5000
5001                case PREVENT_TOUCH_ID:
5002                    if (msg.arg1 == MotionEvent.ACTION_DOWN) {
5003                        mPreventDrag = msg.arg2 == 1;
5004                        if (mPreventDrag) {
5005                            mTouchMode = TOUCH_DONE_MODE;
5006                        }
5007                    }
5008                    break;
5009
5010                case REQUEST_KEYBOARD:
5011                    if (msg.arg1 == 0) {
5012                        hideSoftKeyboard();
5013                    } else {
5014                        displaySoftKeyboard(false);
5015                    }
5016                    break;
5017
5018                default:
5019                    super.handleMessage(msg);
5020                    break;
5021            }
5022        }
5023    }
5024
5025    // Class used to use a dropdown for a <select> element
5026    private class InvokeListBox implements Runnable {
5027        // Whether the listbox allows multiple selection.
5028        private boolean     mMultiple;
5029        // Passed in to a list with multiple selection to tell
5030        // which items are selected.
5031        private int[]       mSelectedArray;
5032        // Passed in to a list with single selection to tell
5033        // where the initial selection is.
5034        private int         mSelection;
5035
5036        private Container[] mContainers;
5037
5038        // Need these to provide stable ids to my ArrayAdapter,
5039        // which normally does not have stable ids. (Bug 1250098)
5040        private class Container extends Object {
5041            String  mString;
5042            boolean mEnabled;
5043            int     mId;
5044
5045            public String toString() {
5046                return mString;
5047            }
5048        }
5049
5050        /**
5051         *  Subclass ArrayAdapter so we can disable OptionGroupLabels,
5052         *  and allow filtering.
5053         */
5054        private class MyArrayListAdapter extends ArrayAdapter<Container> {
5055            public MyArrayListAdapter(Context context, Container[] objects, boolean multiple) {
5056                super(context,
5057                            multiple ? com.android.internal.R.layout.select_dialog_multichoice :
5058                            com.android.internal.R.layout.select_dialog_singlechoice,
5059                            objects);
5060            }
5061
5062            @Override
5063            public boolean hasStableIds() {
5064                // AdapterView's onChanged method uses this to determine whether
5065                // to restore the old state.  Return false so that the old (out
5066                // of date) state does not replace the new, valid state.
5067                return false;
5068            }
5069
5070            private Container item(int position) {
5071                if (position < 0 || position >= getCount()) {
5072                    return null;
5073                }
5074                return (Container) getItem(position);
5075            }
5076
5077            @Override
5078            public long getItemId(int position) {
5079                Container item = item(position);
5080                if (item == null) {
5081                    return -1;
5082                }
5083                return item.mId;
5084            }
5085
5086            @Override
5087            public boolean areAllItemsEnabled() {
5088                return false;
5089            }
5090
5091            @Override
5092            public boolean isEnabled(int position) {
5093                Container item = item(position);
5094                if (item == null) {
5095                    return false;
5096                }
5097                return item.mEnabled;
5098            }
5099        }
5100
5101        private InvokeListBox(String[] array,
5102                boolean[] enabled, int[] selected) {
5103            mMultiple = true;
5104            mSelectedArray = selected;
5105
5106            int length = array.length;
5107            mContainers = new Container[length];
5108            for (int i = 0; i < length; i++) {
5109                mContainers[i] = new Container();
5110                mContainers[i].mString = array[i];
5111                mContainers[i].mEnabled = enabled[i];
5112                mContainers[i].mId = i;
5113            }
5114        }
5115
5116        private InvokeListBox(String[] array, boolean[] enabled, int
5117                selection) {
5118            mSelection = selection;
5119            mMultiple = false;
5120
5121            int length = array.length;
5122            mContainers = new Container[length];
5123            for (int i = 0; i < length; i++) {
5124                mContainers[i] = new Container();
5125                mContainers[i].mString = array[i];
5126                mContainers[i].mEnabled = enabled[i];
5127                mContainers[i].mId = i;
5128            }
5129        }
5130
5131        /*
5132         * Whenever the data set changes due to filtering, this class ensures
5133         * that the checked item remains checked.
5134         */
5135        private class SingleDataSetObserver extends DataSetObserver {
5136            private long        mCheckedId;
5137            private ListView    mListView;
5138            private Adapter     mAdapter;
5139
5140            /*
5141             * Create a new observer.
5142             * @param id The ID of the item to keep checked.
5143             * @param l ListView for getting and clearing the checked states
5144             * @param a Adapter for getting the IDs
5145             */
5146            public SingleDataSetObserver(long id, ListView l, Adapter a) {
5147                mCheckedId = id;
5148                mListView = l;
5149                mAdapter = a;
5150            }
5151
5152            public void onChanged() {
5153                // The filter may have changed which item is checked.  Find the
5154                // item that the ListView thinks is checked.
5155                int position = mListView.getCheckedItemPosition();
5156                long id = mAdapter.getItemId(position);
5157                if (mCheckedId != id) {
5158                    // Clear the ListView's idea of the checked item, since
5159                    // it is incorrect
5160                    mListView.clearChoices();
5161                    // Search for mCheckedId.  If it is in the filtered list,
5162                    // mark it as checked
5163                    int count = mAdapter.getCount();
5164                    for (int i = 0; i < count; i++) {
5165                        if (mAdapter.getItemId(i) == mCheckedId) {
5166                            mListView.setItemChecked(i, true);
5167                            break;
5168                        }
5169                    }
5170                }
5171            }
5172
5173            public void onInvalidate() {}
5174        }
5175
5176        public void run() {
5177            final ListView listView = (ListView) LayoutInflater.from(mContext)
5178                    .inflate(com.android.internal.R.layout.select_dialog, null);
5179            final MyArrayListAdapter adapter = new
5180                    MyArrayListAdapter(mContext, mContainers, mMultiple);
5181            AlertDialog.Builder b = new AlertDialog.Builder(mContext)
5182                    .setView(listView).setCancelable(true)
5183                    .setInverseBackgroundForced(true);
5184
5185            if (mMultiple) {
5186                b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
5187                    public void onClick(DialogInterface dialog, int which) {
5188                        mWebViewCore.sendMessage(
5189                                EventHub.LISTBOX_CHOICES,
5190                                adapter.getCount(), 0,
5191                                listView.getCheckedItemPositions());
5192                    }});
5193                b.setNegativeButton(android.R.string.cancel,
5194                        new DialogInterface.OnClickListener() {
5195                    public void onClick(DialogInterface dialog, int which) {
5196                        mWebViewCore.sendMessage(
5197                                EventHub.SINGLE_LISTBOX_CHOICE, -2, 0);
5198                }});
5199            }
5200            final AlertDialog dialog = b.create();
5201            listView.setAdapter(adapter);
5202            listView.setFocusableInTouchMode(true);
5203            // There is a bug (1250103) where the checks in a ListView with
5204            // multiple items selected are associated with the positions, not
5205            // the ids, so the items do not properly retain their checks when
5206            // filtered.  Do not allow filtering on multiple lists until
5207            // that bug is fixed.
5208
5209            listView.setTextFilterEnabled(!mMultiple);
5210            if (mMultiple) {
5211                listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
5212                int length = mSelectedArray.length;
5213                for (int i = 0; i < length; i++) {
5214                    listView.setItemChecked(mSelectedArray[i], true);
5215                }
5216            } else {
5217                listView.setOnItemClickListener(new OnItemClickListener() {
5218                    public void onItemClick(AdapterView parent, View v,
5219                            int position, long id) {
5220                        mWebViewCore.sendMessage(
5221                                EventHub.SINGLE_LISTBOX_CHOICE, (int)id, 0);
5222                        dialog.dismiss();
5223                    }
5224                });
5225                if (mSelection != -1) {
5226                    listView.setSelection(mSelection);
5227                    listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
5228                    listView.setItemChecked(mSelection, true);
5229                    DataSetObserver observer = new SingleDataSetObserver(
5230                            adapter.getItemId(mSelection), listView, adapter);
5231                    adapter.registerDataSetObserver(observer);
5232                }
5233            }
5234            dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
5235                public void onCancel(DialogInterface dialog) {
5236                    mWebViewCore.sendMessage(
5237                                EventHub.SINGLE_LISTBOX_CHOICE, -2, 0);
5238                }
5239            });
5240            dialog.show();
5241        }
5242    }
5243
5244    /*
5245     * Request a dropdown menu for a listbox with multiple selection.
5246     *
5247     * @param array Labels for the listbox.
5248     * @param enabledArray  Which positions are enabled.
5249     * @param selectedArray Which positions are initally selected.
5250     */
5251    void requestListBox(String[] array, boolean[]enabledArray, int[]
5252            selectedArray) {
5253        mPrivateHandler.post(
5254                new InvokeListBox(array, enabledArray, selectedArray));
5255    }
5256
5257    /*
5258     * Request a dropdown menu for a listbox with single selection or a single
5259     * <select> element.
5260     *
5261     * @param array Labels for the listbox.
5262     * @param enabledArray  Which positions are enabled.
5263     * @param selection Which position is initally selected.
5264     */
5265    void requestListBox(String[] array, boolean[]enabledArray, int selection) {
5266        mPrivateHandler.post(
5267                new InvokeListBox(array, enabledArray, selection));
5268    }
5269
5270    // called by JNI
5271    private void sendMoveMouse(int frame, int node, int x, int y) {
5272        mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE,
5273                new WebViewCore.CursorData(frame, node, x, y));
5274    }
5275
5276    /*
5277     * Send a mouse move event to the webcore thread.
5278     *
5279     * @param removeFocus Pass true if the "mouse" cursor is now over a node
5280     *                    which wants key events, but it is not the focus. This
5281     *                    will make the visual appear as though nothing is in
5282     *                    focus.  Remove the WebTextView, if present, and stop
5283     *                    drawing the blinking caret.
5284     * called by JNI
5285     */
5286    private void sendMoveMouseIfLatest(boolean removeFocus) {
5287        if (removeFocus) {
5288            clearTextEntry();
5289            setFocusControllerInactive();
5290        }
5291        mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE_IF_LATEST,
5292                cursorData());
5293    }
5294
5295    // called by JNI
5296    private void sendMotionUp(int touchGeneration,
5297            int frame, int node, int x, int y) {
5298        WebViewCore.TouchUpData touchUpData = new WebViewCore.TouchUpData();
5299        touchUpData.mMoveGeneration = touchGeneration;
5300        touchUpData.mFrame = frame;
5301        touchUpData.mNode = node;
5302        touchUpData.mX = x;
5303        touchUpData.mY = y;
5304        mWebViewCore.sendMessage(EventHub.TOUCH_UP, touchUpData);
5305    }
5306
5307
5308    private int getScaledMaxXScroll() {
5309        int width;
5310        if (mHeightCanMeasure == false) {
5311            width = getViewWidth() / 4;
5312        } else {
5313            Rect visRect = new Rect();
5314            calcOurVisibleRect(visRect);
5315            width = visRect.width() / 2;
5316        }
5317        // FIXME the divisor should be retrieved from somewhere
5318        return viewToContentX(width);
5319    }
5320
5321    private int getScaledMaxYScroll() {
5322        int height;
5323        if (mHeightCanMeasure == false) {
5324            height = getViewHeight() / 4;
5325        } else {
5326            Rect visRect = new Rect();
5327            calcOurVisibleRect(visRect);
5328            height = visRect.height() / 2;
5329        }
5330        // FIXME the divisor should be retrieved from somewhere
5331        // the closest thing today is hard-coded into ScrollView.java
5332        // (from ScrollView.java, line 363)   int maxJump = height/2;
5333        return Math.round(height * mInvActualScale);
5334    }
5335
5336    /**
5337     * Called by JNI to invalidate view
5338     */
5339    private void viewInvalidate() {
5340        invalidate();
5341    }
5342
5343    // return true if the key was handled
5344    private boolean navHandledKey(int keyCode, int count, boolean noScroll,
5345            long time, boolean ignorePlugin) {
5346        if (mNativeClass == 0) {
5347            return false;
5348        }
5349        if (ignorePlugin == false && nativePluginEatsNavKey()) {
5350            KeyEvent event = new KeyEvent(time, time, KeyEvent.ACTION_DOWN
5351                , keyCode, count, (mShiftIsPressed ? KeyEvent.META_SHIFT_ON : 0)
5352                | (false ? KeyEvent.META_ALT_ON : 0) // FIXME
5353                | (false ? KeyEvent.META_SYM_ON : 0) // FIXME
5354                , 0, 0, 0);
5355            mWebViewCore.sendMessage(EventHub.KEY_DOWN, event);
5356            mWebViewCore.sendMessage(EventHub.KEY_UP, event);
5357            return true;
5358        }
5359        mLastCursorTime = time;
5360        mLastCursorBounds = nativeGetCursorRingBounds();
5361        boolean keyHandled
5362                = nativeMoveCursor(keyCode, count, noScroll) == false;
5363        if (DebugFlags.WEB_VIEW) {
5364            Log.v(LOGTAG, "navHandledKey mLastCursorBounds=" + mLastCursorBounds
5365                    + " mLastCursorTime=" + mLastCursorTime
5366                    + " handled=" + keyHandled);
5367        }
5368        if (keyHandled == false || mHeightCanMeasure == false) {
5369            return keyHandled;
5370        }
5371        Rect contentCursorRingBounds = nativeGetCursorRingBounds();
5372        if (contentCursorRingBounds.isEmpty()) return keyHandled;
5373        Rect viewCursorRingBounds = contentToViewRect(contentCursorRingBounds);
5374        Rect visRect = new Rect();
5375        calcOurVisibleRect(visRect);
5376        Rect outset = new Rect(visRect);
5377        int maxXScroll = visRect.width() / 2;
5378        int maxYScroll = visRect.height() / 2;
5379        outset.inset(-maxXScroll, -maxYScroll);
5380        if (Rect.intersects(outset, viewCursorRingBounds) == false) {
5381            return keyHandled;
5382        }
5383        // FIXME: Necessary because ScrollView/ListView do not scroll left/right
5384        int maxH = Math.min(viewCursorRingBounds.right - visRect.right,
5385                maxXScroll);
5386        if (maxH > 0) {
5387            pinScrollBy(maxH, 0, true, 0);
5388        } else {
5389            maxH = Math.max(viewCursorRingBounds.left - visRect.left,
5390                    -maxXScroll);
5391            if (maxH < 0) {
5392                pinScrollBy(maxH, 0, true, 0);
5393            }
5394        }
5395        if (mLastCursorBounds.isEmpty()) return keyHandled;
5396        if (mLastCursorBounds.equals(contentCursorRingBounds)) {
5397            return keyHandled;
5398        }
5399        if (DebugFlags.WEB_VIEW) {
5400            Log.v(LOGTAG, "navHandledKey contentCursorRingBounds="
5401                    + contentCursorRingBounds);
5402        }
5403        requestRectangleOnScreen(viewCursorRingBounds);
5404        mUserScroll = true;
5405        return keyHandled;
5406    }
5407
5408    /**
5409     * Set the background color. It's white by default. Pass
5410     * zero to make the view transparent.
5411     * @param color   the ARGB color described by Color.java
5412     */
5413    public void setBackgroundColor(int color) {
5414        mBackgroundColor = color;
5415        mWebViewCore.sendMessage(EventHub.SET_BACKGROUND_COLOR, color);
5416    }
5417
5418    public void debugDump() {
5419        nativeDebugDump();
5420        mWebViewCore.sendMessage(EventHub.DUMP_NAVTREE);
5421    }
5422
5423    /**
5424     *  Update our cache with updatedText.
5425     *  @param updatedText  The new text to put in our cache.
5426     */
5427    /* package */ void updateCachedTextfield(String updatedText) {
5428        // Also place our generation number so that when we look at the cache
5429        // we recognize that it is up to date.
5430        nativeUpdateCachedTextfield(updatedText, mTextGeneration);
5431    }
5432
5433    /* package */ native void nativeClearCursor();
5434    private native void     nativeCreate(int ptr);
5435    private native int      nativeCursorFramePointer();
5436    private native Rect     nativeCursorNodeBounds();
5437    /* package */ native int nativeCursorNodePointer();
5438    /* package */ native boolean nativeCursorMatchesFocus();
5439    private native boolean  nativeCursorIntersects(Rect visibleRect);
5440    private native boolean  nativeCursorIsAnchor();
5441    private native boolean  nativeCursorIsPlugin();
5442    private native boolean  nativeCursorIsTextInput();
5443    private native Point    nativeCursorPosition();
5444    private native String   nativeCursorText();
5445    /**
5446     * Returns true if the native cursor node says it wants to handle key events
5447     * (ala plugins). This can only be called if mNativeClass is non-zero!
5448     */
5449    private native boolean  nativeCursorWantsKeyEvents();
5450    private native void     nativeDebugDump();
5451    private native void     nativeDestroy();
5452    private native void     nativeDrawCursorRing(Canvas content);
5453    private native void     nativeDrawMatches(Canvas canvas);
5454    private native void     nativeDrawSelection(Canvas content
5455            , int x, int y, boolean extendSelection);
5456    private native void     nativeDrawSelectionRegion(Canvas content);
5457    private native void     nativeDumpDisplayTree(String urlOrNull);
5458    private native int      nativeFindAll(String findLower, String findUpper);
5459    private native void     nativeFindNext(boolean forward);
5460    private native boolean  nativeFocusCandidateIsPassword();
5461    private native boolean  nativeFocusCandidateIsRtlText();
5462    private native boolean  nativeFocusCandidateIsTextField();
5463    private native boolean  nativeFocusCandidateIsTextInput();
5464    private native int      nativeFocusCandidateMaxLength();
5465    /* package */ native String   nativeFocusCandidateName();
5466    private native Rect     nativeFocusCandidateNodeBounds();
5467    /* package */ native int nativeFocusCandidatePointer();
5468    private native String   nativeFocusCandidateText();
5469    private native int      nativeFocusCandidateTextSize();
5470    /* package */ native int nativeFocusNodePointer();
5471    private native Rect     nativeGetCursorRingBounds();
5472    private native Region   nativeGetSelection();
5473    private native boolean  nativeHasCursorNode();
5474    private native boolean  nativeHasFocusNode();
5475    private native void     nativeHideCursor();
5476    private native String   nativeImageURI(int x, int y);
5477    private native void     nativeInstrumentReport();
5478    /* package */ native void nativeMoveCursorToNextTextInput();
5479    // return true if the page has been scrolled
5480    private native boolean  nativeMotionUp(int x, int y, int slop);
5481    // returns false if it handled the key
5482    private native boolean  nativeMoveCursor(int keyCode, int count,
5483            boolean noScroll);
5484    private native int      nativeMoveGeneration();
5485    private native void     nativeMoveSelection(int x, int y,
5486            boolean extendSelection);
5487    private native boolean  nativePluginEatsNavKey();
5488    // Like many other of our native methods, you must make sure that
5489    // mNativeClass is not null before calling this method.
5490    private native void     nativeRecordButtons(boolean focused,
5491            boolean pressed, boolean invalidate);
5492    private native void     nativeSelectBestAt(Rect rect);
5493    private native void     nativeSetFindIsDown();
5494    private native void     nativeSetFollowedLink(boolean followed);
5495    private native void     nativeSetHeightCanMeasure(boolean measure);
5496    // Returns a value corresponding to CachedFrame::ImeAction
5497    /* package */ native int  nativeTextFieldAction();
5498    /**
5499     * Perform a click on a currently focused text input.  Since it is already
5500     * focused, there is no need to go through the nativeMotionUp code, which
5501     * may change the Cursor.
5502     */
5503    private native void     nativeTextInputMotionUp(int x, int y);
5504    private native int      nativeTextGeneration();
5505    // Never call this version except by updateCachedTextfield(String) -
5506    // we always want to pass in our generation number.
5507    private native void     nativeUpdateCachedTextfield(String updatedText,
5508            int generation);
5509    private native void     nativeUpdatePluginReceivesEvents();
5510    // return NO_LEFTEDGE means failure.
5511    private static final int NO_LEFTEDGE = -1;
5512    private native int      nativeGetBlockLeftEdge(int x, int y, float scale);
5513}
5514