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