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