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