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