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