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