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