WebView.java revision 82aa2f5eecddf723af2e4e134de15bbf5c6b32f4
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_ENTER                   = 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_ENTER", //               = 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        nativeClearFocus(-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        nativeClearFocus(-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 focus 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    public void requestFocusNodeHref(Message hrefMsg) {
1606        if (hrefMsg == null || mNativeClass == 0) {
1607            return;
1608        }
1609        if (nativeCursorIsAnchor()) {
1610            // NOTE: We may already have the url of the anchor stored in
1611            // node.mText but it may be out of date or the caller may want
1612            // to know about javascript urls.
1613            mWebViewCore.sendMessage(EventHub.REQUEST_FOCUS_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 focus is 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 || mGotEnterDown, 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 drawFocus) {
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 (drawFocus) {
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 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 we do not have focus, do nothing until we gain 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 || (!nativeFocusIsTextInput()
2974                && !nativeCursorIsTextInput())) {
2975            if (alreadyThere) {
2976                mWebTextView.remove();
2977            }
2978            return;
2979        }
2980        // At this point, we know we have found an input field, so go ahead
2981        // and create the WebTextView if necessary.
2982        if (mWebTextView == null) {
2983            mWebTextView = new WebTextView(mContext, WebView.this);
2984            // Initialize our generation number.
2985            mTextGeneration = 0;
2986        }
2987        mWebTextView.setTextSize(contentToView(nativeFocusTextSize()));
2988        Rect visibleRect = sendOurVisibleRect();
2989        // Note that sendOurVisibleRect calls viewToContent, so the coordinates
2990        // should be in content coordinates.
2991        Rect bounds = nativeFocusNodeBounds();
2992        if (!Rect.intersects(bounds, visibleRect)) {
2993            // Node is not on screen, so do not bother.
2994            return;
2995        }
2996        String text = nativeFocusText();
2997        int nodePointer = nativeFocusNodePointer();
2998        if (alreadyThere && mWebTextView.isSameTextField(nodePointer)) {
2999            // It is possible that we have the same textfield, but it has moved,
3000            // i.e. In the case of opening/closing the screen.
3001            // In that case, we need to set the dimensions, but not the other
3002            // aspects.
3003            // We also need to restore the selection, which gets wrecked by
3004            // calling setTextEntryRect.
3005            Spannable spannable = (Spannable) mWebTextView.getText();
3006            int start = Selection.getSelectionStart(spannable);
3007            int end = Selection.getSelectionEnd(spannable);
3008            // If the text has been changed by webkit, update it.  However, if
3009            // there has been more UI text input, ignore it.  We will receive
3010            // another update when that text is recognized.
3011            if (text != null && !text.equals(spannable.toString())
3012                    && nativeTextGeneration() == mTextGeneration) {
3013                mWebTextView.setTextAndKeepSelection(text);
3014            } else {
3015                Selection.setSelection(spannable, start, end);
3016            }
3017        } else {
3018            Rect vBox = contentToView(bounds);
3019            mWebTextView.setRect(vBox.left, vBox.top, vBox.width(),
3020                    vBox.height());
3021            mWebTextView.setGravity(nativeFocusIsRtlText() ? Gravity.RIGHT :
3022                    Gravity.NO_GRAVITY);
3023            // this needs to be called before update adapter thread starts to
3024            // ensure the mWebTextView has the same node pointer
3025            mWebTextView.setNodePointer(nodePointer);
3026            int maxLength = -1;
3027            boolean isTextField = nativeFocusIsTextField();
3028            if (isTextField) {
3029                maxLength = nativeFocusMaxLength();
3030                String name = nativeFocusName();
3031                if (mWebViewCore.getSettings().getSaveFormData()
3032                        && name != null) {
3033                    HashMap data = new HashMap();
3034                    data.put("text", text);
3035                    Message update = mPrivateHandler.obtainMessage(
3036                            REQUEST_FORM_DATA, nodePointer, 0, data);
3037                    RequestFormData updater = new RequestFormData(name,
3038                            getUrl(), update);
3039                    Thread t = new Thread(updater);
3040                    t.start();
3041                }
3042            }
3043            mWebTextView.setMaxLength(maxLength);
3044            AutoCompleteAdapter adapter = null;
3045            mWebTextView.setAdapterCustom(adapter);
3046            mWebTextView.setSingleLine(isTextField);
3047            mWebTextView.setInPassword(nativeFocusIsPassword());
3048            if (null == text) {
3049                mWebTextView.setText("", 0, 0);
3050            } else {
3051                // Change to true to enable the old style behavior, where
3052                // entering a textfield/textarea always set the selection to the
3053                // whole field.  This was desirable for the case where the user
3054                // intends to scroll past the field using the trackball.
3055                // However, it causes a problem when replying to emails - the
3056                // user expects the cursor to be at the beginning of the
3057                // textarea.  Testing out a new behavior, where textfields set
3058                // selection at the end, and textareas at the beginning.
3059                if (false) {
3060                    mWebTextView.setText(text, 0, text.length());
3061                } else if (isTextField) {
3062                    int length = text.length();
3063                    mWebTextView.setText(text, length, length);
3064                } else {
3065                    mWebTextView.setText(text, 0, 0);
3066                }
3067            }
3068            mWebTextView.requestFocus();
3069        }
3070    }
3071
3072    /*
3073     * This class requests an Adapter for the WebTextView which shows past
3074     * entries stored in the database.  It is a Runnable so that it can be done
3075     * in its own thread, without slowing down the UI.
3076     */
3077    private class RequestFormData implements Runnable {
3078        private String mName;
3079        private String mUrl;
3080        private Message mUpdateMessage;
3081
3082        public RequestFormData(String name, String url, Message msg) {
3083            mName = name;
3084            mUrl = url;
3085            mUpdateMessage = msg;
3086        }
3087
3088        public void run() {
3089            ArrayList<String> pastEntries = mDatabase.getFormData(mUrl, mName);
3090            if (pastEntries.size() > 0) {
3091                AutoCompleteAdapter adapter = new
3092                        AutoCompleteAdapter(mContext, pastEntries);
3093                ((HashMap) mUpdateMessage.obj).put("adapter", adapter);
3094                mUpdateMessage.sendToTarget();
3095            }
3096        }
3097    }
3098
3099    // This is used to determine long press with the enter key, or
3100    // a center key.  Does not affect long press with the trackball/touch.
3101    private boolean mGotEnterDown = false;
3102
3103    @Override
3104    public boolean onKeyDown(int keyCode, KeyEvent event) {
3105        if (DebugFlags.WEB_VIEW) {
3106            Log.v(LOGTAG, "keyDown at " + System.currentTimeMillis()
3107                    + ", " + event);
3108        }
3109
3110        if (mNativeClass == 0) {
3111            return false;
3112        }
3113
3114        // do this hack up front, so it always works, regardless of touch-mode
3115        if (AUTO_REDRAW_HACK && (keyCode == KeyEvent.KEYCODE_CALL)) {
3116            mAutoRedraw = !mAutoRedraw;
3117            if (mAutoRedraw) {
3118                invalidate();
3119            }
3120            return true;
3121        }
3122
3123        // Bubble up the key event if
3124        // 1. it is a system key; or
3125        // 2. the host application wants to handle it; or
3126        // 3. webview is in scroll-zoom state;
3127        if (event.isSystem()
3128                || mCallbackProxy.uiOverrideKeyEvent(event)
3129                || (mTouchMode >= FIRST_SCROLL_ZOOM && mTouchMode <= LAST_SCROLL_ZOOM)) {
3130            return false;
3131        }
3132
3133        if (mShiftIsPressed == false && nativeCursorWantsKeyEvents() == false
3134                && (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
3135                || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT)) {
3136            mExtendSelection = false;
3137            mShiftIsPressed = true;
3138            if (nativeHasCursorNode()) {
3139                Rect rect = nativeCursorNodeBounds();
3140                mSelectX = contentToView(rect.left);
3141                mSelectY = contentToView(rect.top);
3142            } else {
3143                mSelectX = mScrollX + (int) mLastTouchX;
3144                mSelectY = mScrollY + (int) mLastTouchY;
3145            }
3146            int contentX = viewToContent((int) mLastTouchX + mScrollX);
3147            int contentY = viewToContent((int) mLastTouchY + mScrollY);
3148            nativeClearFocus(contentX, contentY);
3149       }
3150
3151        if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
3152                && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
3153            // always handle the navigation keys in the UI thread
3154            switchOutDrawHistory();
3155            if (navHandledKey(keyCode, 1, false, event.getEventTime())) {
3156                playSoundEffect(keyCodeToSoundsEffect(keyCode));
3157                return true;
3158            }
3159            // Bubble up the key event as WebView doesn't handle it
3160            return false;
3161        }
3162
3163        if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
3164                || keyCode == KeyEvent.KEYCODE_ENTER) {
3165            switchOutDrawHistory();
3166            if (event.getRepeatCount() == 0) {
3167                mGotEnterDown = true;
3168                mPrivateHandler.sendMessageDelayed(mPrivateHandler
3169                        .obtainMessage(LONG_PRESS_ENTER), LONG_PRESS_TIMEOUT);
3170                // Already checked mNativeClass, so we do not need to check it
3171                // again.
3172                nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true);
3173                return true;
3174            }
3175            // Bubble up the key event as WebView doesn't handle it
3176            return false;
3177        }
3178
3179        if (keyCode != KeyEvent.KEYCODE_SHIFT_LEFT
3180                && keyCode != KeyEvent.KEYCODE_SHIFT_RIGHT) {
3181            // turn off copy select if a shift-key combo is pressed
3182            mExtendSelection = mShiftIsPressed = false;
3183            if (mTouchMode == TOUCH_SELECT_MODE) {
3184                mTouchMode = TOUCH_INIT_MODE;
3185            }
3186        }
3187
3188        if (getSettings().getNavDump()) {
3189            switch (keyCode) {
3190                case KeyEvent.KEYCODE_4:
3191                    // "/data/data/com.android.browser/displayTree.txt"
3192                    nativeDumpDisplayTree(getUrl());
3193                    break;
3194                case KeyEvent.KEYCODE_5:
3195                case KeyEvent.KEYCODE_6:
3196                    // 5: dump the dom tree to the file
3197                    // "/data/data/com.android.browser/domTree.txt"
3198                    // 6: dump the dom tree to the adb log
3199                    mWebViewCore.sendMessage(EventHub.DUMP_DOMTREE,
3200                            (keyCode == KeyEvent.KEYCODE_5) ? 1 : 0, 0);
3201                    break;
3202                case KeyEvent.KEYCODE_7:
3203                case KeyEvent.KEYCODE_8:
3204                    // 7: dump the render tree to the file
3205                    // "/data/data/com.android.browser/renderTree.txt"
3206                    // 8: dump the render tree to the adb log
3207                    mWebViewCore.sendMessage(EventHub.DUMP_RENDERTREE,
3208                            (keyCode == KeyEvent.KEYCODE_7) ? 1 : 0, 0);
3209                    break;
3210                case KeyEvent.KEYCODE_9:
3211                    nativeInstrumentReport();
3212                    return true;
3213            }
3214        }
3215
3216        // TODO: should we pass all the keys to DOM or check the meta tag
3217        if (nativeCursorWantsKeyEvents() || true) {
3218            // pass the key to DOM
3219            mWebViewCore.sendMessage(EventHub.KEY_DOWN, event);
3220            // return true as DOM handles the key
3221            return true;
3222        }
3223
3224        // Bubble up the key event as WebView doesn't handle it
3225        return false;
3226    }
3227
3228    @Override
3229    public boolean onKeyUp(int keyCode, KeyEvent event) {
3230        if (DebugFlags.WEB_VIEW) {
3231            Log.v(LOGTAG, "keyUp at " + System.currentTimeMillis()
3232                    + ", " + event);
3233        }
3234
3235        if (mNativeClass == 0) {
3236            return false;
3237        }
3238
3239        // special CALL handling when focus node's href is "tel:XXX"
3240        if (keyCode == KeyEvent.KEYCODE_CALL && nativeHasCursorNode()) {
3241            String text = nativeCursorText();
3242            if (!nativeCursorIsTextInput() && text != null
3243                    && text.startsWith(SCHEME_TEL)) {
3244                Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(text));
3245                getContext().startActivity(intent);
3246                return true;
3247            }
3248        }
3249
3250        // Bubble up the key event if
3251        // 1. it is a system key; or
3252        // 2. the host application wants to handle it;
3253        if (event.isSystem() || mCallbackProxy.uiOverrideKeyEvent(event)) {
3254            return false;
3255        }
3256
3257        // special handling in scroll_zoom state
3258        if (mTouchMode >= FIRST_SCROLL_ZOOM && mTouchMode <= LAST_SCROLL_ZOOM) {
3259            if (KeyEvent.KEYCODE_DPAD_CENTER == keyCode
3260                    && mTouchMode != SCROLL_ZOOM_ANIMATION_IN) {
3261                setZoomScrollIn();
3262                mTouchMode = SCROLL_ZOOM_ANIMATION_IN;
3263                invalidate();
3264                return true;
3265            }
3266            return false;
3267        }
3268
3269        if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
3270                || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
3271            if (commitCopy()) {
3272                return true;
3273            }
3274        }
3275
3276        if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
3277                && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
3278            // always handle the navigation keys in the UI thread
3279            // Bubble up the key event as WebView doesn't handle it
3280            return false;
3281        }
3282
3283        if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
3284                || keyCode == KeyEvent.KEYCODE_ENTER) {
3285            // remove the long press message first
3286            mPrivateHandler.removeMessages(LONG_PRESS_ENTER);
3287            mGotEnterDown = false;
3288
3289            if (KeyEvent.KEYCODE_DPAD_CENTER == keyCode) {
3290                if (mShiftIsPressed) {
3291                    return false;
3292                }
3293                if (getSettings().supportZoom()) {
3294                    if (mTouchMode == TOUCH_DOUBLECLICK_MODE) {
3295                        zoomScrollOut();
3296                    } else {
3297                        if (DebugFlags.WEB_VIEW) {
3298                            Log.v(LOGTAG, "TOUCH_DOUBLECLICK_MODE");
3299                        }
3300                        mPrivateHandler.sendMessageDelayed(mPrivateHandler
3301                                .obtainMessage(SWITCH_TO_CLICK), TAP_TIMEOUT);
3302                        mTouchMode = TOUCH_DOUBLECLICK_MODE;
3303                    }
3304                    return true;
3305                }
3306            }
3307
3308            Rect visibleRect = sendOurVisibleRect();
3309            // Note that sendOurVisibleRect calls viewToContent, so the
3310            // coordinates should be in content coordinates.
3311            if (nativeCursorIntersects(visibleRect)) {
3312                nativeSetFollowedLink(true);
3313                mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, cursorData());
3314                playSoundEffect(SoundEffectConstants.CLICK);
3315                return true;
3316            } else if (nativeHasCursorNode()) {
3317                return true;
3318            }
3319            // Bubble up the key event as WebView doesn't handle it
3320            return false;
3321        }
3322
3323        // TODO: should we pass all the keys to DOM or check the meta tag
3324        if (nativeCursorWantsKeyEvents() || true) {
3325            // pass the key to DOM
3326            mWebViewCore.sendMessage(EventHub.KEY_UP, event);
3327            // return true as DOM handles the key
3328            return true;
3329        }
3330
3331        // Bubble up the key event as WebView doesn't handle it
3332        return false;
3333    }
3334
3335    /**
3336     * @hide
3337     */
3338    public void emulateShiftHeld() {
3339        mExtendSelection = false;
3340        mShiftIsPressed = true;
3341        int contentX = viewToContent((int) mLastTouchX + mScrollX);
3342        int contentY = viewToContent((int) mLastTouchY + mScrollY);
3343        nativeClearFocus(contentX, contentY);
3344    }
3345
3346    private boolean commitCopy() {
3347        boolean copiedSomething = false;
3348        if (mExtendSelection) {
3349            // copy region so core operates on copy without touching orig.
3350            Region selection = new Region(nativeGetSelection());
3351            if (selection.isEmpty() == false) {
3352                Toast.makeText(mContext
3353                        , com.android.internal.R.string.text_copied
3354                        , Toast.LENGTH_SHORT).show();
3355                mWebViewCore.sendMessage(EventHub.GET_SELECTION, selection);
3356                copiedSomething = true;
3357            }
3358            mExtendSelection = false;
3359        }
3360        mShiftIsPressed = false;
3361        if (mTouchMode == TOUCH_SELECT_MODE) {
3362            mTouchMode = TOUCH_INIT_MODE;
3363        }
3364        return copiedSomething;
3365    }
3366
3367    // Set this as a hierarchy change listener so we can know when this view
3368    // is removed and still have access to our parent.
3369    @Override
3370    protected void onAttachedToWindow() {
3371        super.onAttachedToWindow();
3372        ViewParent parent = getParent();
3373        if (parent instanceof ViewGroup) {
3374            ViewGroup p = (ViewGroup) parent;
3375            p.setOnHierarchyChangeListener(this);
3376        }
3377    }
3378
3379    @Override
3380    protected void onDetachedFromWindow() {
3381        super.onDetachedFromWindow();
3382        ViewParent parent = getParent();
3383        if (parent instanceof ViewGroup) {
3384            ViewGroup p = (ViewGroup) parent;
3385            p.setOnHierarchyChangeListener(null);
3386        }
3387
3388        // Clean up the zoom controller
3389        mZoomButtonsController.setVisible(false);
3390    }
3391
3392    // Implementation for OnHierarchyChangeListener
3393    public void onChildViewAdded(View parent, View child) {}
3394
3395    public void onChildViewRemoved(View p, View child) {
3396        if (child == this) {
3397            if (inEditingMode()) {
3398                clearTextEntry();
3399                mNeedsRebuildWebTextView = true;
3400            }
3401        }
3402    }
3403
3404    /**
3405     * @deprecated WebView should not have implemented
3406     * ViewTreeObserver.OnGlobalFocusChangeListener.  This method
3407     * does nothing now.
3408     */
3409    @Deprecated
3410    public void onGlobalFocusChanged(View oldFocus, View newFocus) {
3411    }
3412
3413    // To avoid drawing the cursor ring, and remove the TextView when our window
3414    // loses focus.
3415    @Override
3416    public void onWindowFocusChanged(boolean hasWindowFocus) {
3417        if (hasWindowFocus) {
3418            if (hasFocus()) {
3419                // If our window regained focus, and we have focus, then begin
3420                // drawing the cursor ring, and restore the TextView if
3421                // necessary.
3422                mDrawCursorRing = true;
3423                if (mNeedsRebuildWebTextView) {
3424                    rebuildWebTextView();
3425                }
3426                if (mNativeClass != 0) {
3427                    nativeRecordButtons(true, false, true);
3428                }
3429                setFocusControllerActive(true);
3430            } else {
3431                // If our window gained focus, but we do not have it, do not
3432                // draw the cursor ring.
3433                mDrawCursorRing = false;
3434                // We do not call nativeRecordButtons here because we assume
3435                // that when we lost focus, or window focus, it got called with
3436                // false for the first parameter
3437            }
3438        } else {
3439            if (getSettings().getBuiltInZoomControls() && !mZoomButtonsController.isVisible()) {
3440                /*
3441                 * The zoom controls come in their own window, so our window
3442                 * loses focus. Our policy is to not draw the cursor ring if
3443                 * our window is not focused, but this is an exception since
3444                 * the user can still navigate the web page with the zoom
3445                 * controls showing.
3446                 */
3447                // If our window has lost focus, stop drawing the cursor ring
3448                mDrawCursorRing = false;
3449            }
3450            mGotKeyDown = false;
3451            mShiftIsPressed = false;
3452            if (mNativeClass != 0) {
3453                nativeRecordButtons(false, false, true);
3454            }
3455            setFocusControllerActive(false);
3456        }
3457        invalidate();
3458        super.onWindowFocusChanged(hasWindowFocus);
3459    }
3460
3461    /*
3462     * Pass a message to WebCore Thread, determining whether the WebCore::Page's
3463     * FocusController is "active" so that it will draw the blinking cursor.
3464     */
3465    private void setFocusControllerActive(boolean active) {
3466        if (mWebViewCore != null) {
3467            mWebViewCore.sendMessage(EventHub.SET_ACTIVE, active ? 1 : 0, 0);
3468        }
3469    }
3470
3471    @Override
3472    protected void onFocusChanged(boolean focused, int direction,
3473            Rect previouslyFocusedRect) {
3474        if (DebugFlags.WEB_VIEW) {
3475            Log.v(LOGTAG, "MT focusChanged " + focused + ", " + direction);
3476        }
3477        if (focused) {
3478            // When we regain focus, if we have window focus, resume drawing
3479            // the cursor ring, and add the TextView if necessary.
3480            if (hasWindowFocus()) {
3481                mDrawCursorRing = true;
3482                if (mNeedsRebuildWebTextView) {
3483                    rebuildWebTextView();
3484                    mNeedsRebuildWebTextView = false;
3485                }
3486                if (mNativeClass != 0) {
3487                    nativeRecordButtons(true, false, true);
3488                }
3489                // FIXME: This is unnecessary if we are gaining focus from the
3490                // WebTextView.  How can we tell if it was the last thing in
3491                // focus?
3492                setFocusControllerActive(true);
3493            //} else {
3494                // The WebView has gained focus while we do not have
3495                // windowfocus.  When our window lost focus, we should have
3496                // called nativeRecordButtons(false...)
3497            }
3498        } else {
3499            // When we lost focus, unless focus went to the TextView (which is
3500            // true if we are in editing mode), stop drawing the cursor ring.
3501            if (!inEditingMode()) {
3502                mDrawCursorRing = false;
3503                if (mNativeClass != 0) {
3504                    nativeRecordButtons(false, false, true);
3505                }
3506                setFocusControllerActive(false);
3507            }
3508            mGotKeyDown = false;
3509        }
3510
3511        super.onFocusChanged(focused, direction, previouslyFocusedRect);
3512    }
3513
3514    @Override
3515    protected void onSizeChanged(int w, int h, int ow, int oh) {
3516        super.onSizeChanged(w, h, ow, oh);
3517        // Center zooming to the center of the screen.
3518        mZoomCenterX = getViewWidth() * .5f;
3519        mZoomCenterY = getViewHeight() * .5f;
3520
3521        // update mMinZoomScale if the minimum zoom scale is not fixed
3522        if (!mMinZoomScaleFixed) {
3523            mMinZoomScale = (float) getViewWidth()
3524                    / Math.max(ZOOM_OUT_WIDTH, mContentWidth);
3525        }
3526
3527        // we always force, in case our height changed, in which case we still
3528        // want to send the notification over to webkit
3529        setNewZoomScale(mActualScale, true);
3530    }
3531
3532    @Override
3533    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
3534        super.onScrollChanged(l, t, oldl, oldt);
3535        sendOurVisibleRect();
3536    }
3537
3538
3539    @Override
3540    public boolean dispatchKeyEvent(KeyEvent event) {
3541        boolean dispatch = true;
3542
3543        if (!inEditingMode()) {
3544            if (event.getAction() == KeyEvent.ACTION_DOWN) {
3545                mGotKeyDown = true;
3546            } else {
3547                if (!mGotKeyDown) {
3548                    /*
3549                     * We got a key up for which we were not the recipient of
3550                     * the original key down. Don't give it to the view.
3551                     */
3552                    dispatch = false;
3553                }
3554                mGotKeyDown = false;
3555            }
3556        }
3557
3558        if (dispatch) {
3559            return super.dispatchKeyEvent(event);
3560        } else {
3561            // We didn't dispatch, so let something else handle the key
3562            return false;
3563        }
3564    }
3565
3566    // Here are the snap align logic:
3567    // 1. If it starts nearly horizontally or vertically, snap align;
3568    // 2. If there is a dramitic direction change, let it go;
3569    // 3. If there is a same direction back and forth, lock it.
3570
3571    // adjustable parameters
3572    private int mMinLockSnapReverseDistance;
3573    private static final float MAX_SLOPE_FOR_DIAG = 1.5f;
3574    private static final int MIN_BREAK_SNAP_CROSS_DISTANCE = 80;
3575
3576    @Override
3577    public boolean onTouchEvent(MotionEvent ev) {
3578        if (mNativeClass == 0 || !isClickable() || !isLongClickable()) {
3579            return false;
3580        }
3581
3582        if (DebugFlags.WEB_VIEW) {
3583            Log.v(LOGTAG, ev + " at " + ev.getEventTime() + " mTouchMode="
3584                    + mTouchMode);
3585        }
3586
3587        int action = ev.getAction();
3588        float x = ev.getX();
3589        float y = ev.getY();
3590        long eventTime = ev.getEventTime();
3591
3592        // Due to the touch screen edge effect, a touch closer to the edge
3593        // always snapped to the edge. As getViewWidth() can be different from
3594        // getWidth() due to the scrollbar, adjusting the point to match
3595        // getViewWidth(). Same applied to the height.
3596        if (x > getViewWidth() - 1) {
3597            x = getViewWidth() - 1;
3598        }
3599        if (y > getViewHeight() - 1) {
3600            y = getViewHeight() - 1;
3601        }
3602
3603        // pass the touch events from UI thread to WebCore thread
3604        if (mForwardTouchEvents && mTouchMode != SCROLL_ZOOM_OUT
3605                && mTouchMode != SCROLL_ZOOM_ANIMATION_IN
3606                && mTouchMode != SCROLL_ZOOM_ANIMATION_OUT
3607                && (action != MotionEvent.ACTION_MOVE ||
3608                        eventTime - mLastSentTouchTime > TOUCH_SENT_INTERVAL)) {
3609            WebViewCore.TouchEventData ted = new WebViewCore.TouchEventData();
3610            ted.mAction = action;
3611            ted.mX = viewToContent((int) x + mScrollX);
3612            ted.mY = viewToContent((int) y + mScrollY);
3613            mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
3614            mLastSentTouchTime = eventTime;
3615        }
3616
3617        int deltaX = (int) (mLastTouchX - x);
3618        int deltaY = (int) (mLastTouchY - y);
3619
3620        switch (action) {
3621            case MotionEvent.ACTION_DOWN: {
3622                if (mTouchMode == SCROLL_ZOOM_ANIMATION_IN
3623                        || mTouchMode == SCROLL_ZOOM_ANIMATION_OUT) {
3624                    // no interaction while animation is in progress
3625                    break;
3626                } else if (mTouchMode == SCROLL_ZOOM_OUT) {
3627                    mLastScrollX = mZoomScrollX;
3628                    mLastScrollY = mZoomScrollY;
3629                    // If two taps are close, ignore the first tap
3630                } else if (!mScroller.isFinished()) {
3631                    mScroller.abortAnimation();
3632                    mTouchMode = TOUCH_DRAG_START_MODE;
3633                    mPrivateHandler.removeMessages(RESUME_WEBCORE_UPDATE);
3634                } else if (mShiftIsPressed) {
3635                    mSelectX = mScrollX + (int) x;
3636                    mSelectY = mScrollY + (int) y;
3637                    mTouchMode = TOUCH_SELECT_MODE;
3638                    if (DebugFlags.WEB_VIEW) {
3639                        Log.v(LOGTAG, "select=" + mSelectX + "," + mSelectY);
3640                    }
3641                    nativeMoveSelection(viewToContent(mSelectX)
3642                            , viewToContent(mSelectY), false);
3643                    mTouchSelection = mExtendSelection = true;
3644                } else {
3645                    mTouchMode = TOUCH_INIT_MODE;
3646                    mPreventDrag = mForwardTouchEvents;
3647                    if (mLogEvent && eventTime - mLastTouchUpTime < 1000) {
3648                        EventLog.writeEvent(EVENT_LOG_DOUBLE_TAP_DURATION,
3649                                (eventTime - mLastTouchUpTime), eventTime);
3650                    }
3651                }
3652                // Trigger the link
3653                if (mTouchMode == TOUCH_INIT_MODE) {
3654                    mPrivateHandler.sendMessageDelayed(mPrivateHandler
3655                            .obtainMessage(SWITCH_TO_SHORTPRESS), TAP_TIMEOUT);
3656                }
3657                // Remember where the motion event started
3658                mLastTouchX = x;
3659                mLastTouchY = y;
3660                mLastTouchTime = eventTime;
3661                mVelocityTracker = VelocityTracker.obtain();
3662                mSnapScrollMode = SNAP_NONE;
3663                break;
3664            }
3665            case MotionEvent.ACTION_MOVE: {
3666                if (mTouchMode == TOUCH_DONE_MODE
3667                        || mTouchMode == SCROLL_ZOOM_ANIMATION_IN
3668                        || mTouchMode == SCROLL_ZOOM_ANIMATION_OUT) {
3669                    // no dragging during scroll zoom animation
3670                    break;
3671                }
3672                if (mTouchMode == SCROLL_ZOOM_OUT) {
3673                    // while fully zoomed out, move the virtual window
3674                    moveZoomScrollWindow(x, y);
3675                    break;
3676                }
3677                mVelocityTracker.addMovement(ev);
3678
3679                if (mTouchMode != TOUCH_DRAG_MODE) {
3680                    if (mTouchMode == TOUCH_SELECT_MODE) {
3681                        mSelectX = mScrollX + (int) x;
3682                        mSelectY = mScrollY + (int) y;
3683                        if (DebugFlags.WEB_VIEW) {
3684                            Log.v(LOGTAG, "xtend=" + mSelectX + "," + mSelectY);
3685                        }
3686                        nativeMoveSelection(viewToContent(mSelectX)
3687                                , viewToContent(mSelectY), true);
3688                        invalidate();
3689                        break;
3690                    }
3691                    if (mPreventDrag || (deltaX * deltaX + deltaY * deltaY)
3692                            < mTouchSlopSquare) {
3693                        break;
3694                    }
3695
3696                    if (mTouchMode == TOUCH_SHORTPRESS_MODE
3697                            || mTouchMode == TOUCH_SHORTPRESS_START_MODE) {
3698                        mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
3699                    } else if (mTouchMode == TOUCH_INIT_MODE) {
3700                        mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
3701                    }
3702
3703                    // if it starts nearly horizontal or vertical, enforce it
3704                    int ax = Math.abs(deltaX);
3705                    int ay = Math.abs(deltaY);
3706                    if (ax > MAX_SLOPE_FOR_DIAG * ay) {
3707                        mSnapScrollMode = SNAP_X;
3708                        mSnapPositive = deltaX > 0;
3709                    } else if (ay > MAX_SLOPE_FOR_DIAG * ax) {
3710                        mSnapScrollMode = SNAP_Y;
3711                        mSnapPositive = deltaY > 0;
3712                    }
3713
3714                    mTouchMode = TOUCH_DRAG_MODE;
3715                    WebViewCore.pauseUpdate(mWebViewCore);
3716                    int contentX = viewToContent((int) x + mScrollX);
3717                    int contentY = viewToContent((int) y + mScrollY);
3718                    nativeClearFocus(contentX, contentY);
3719                    // remove the zoom anchor if there is any
3720                    if (mZoomScale != 0) {
3721                        mWebViewCore
3722                                .sendMessage(EventHub.SET_SNAP_ANCHOR, 0, 0);
3723                    }
3724                    WebSettings settings = getSettings();
3725                    if (settings.supportZoom()
3726                            && settings.getBuiltInZoomControls()
3727                            && !mZoomButtonsController.isVisible()
3728                            && (canZoomScrollOut() ||
3729                                    mMinZoomScale < mMaxZoomScale)) {
3730                        mZoomButtonsController.setVisible(true);
3731                    }
3732                }
3733
3734                // do pan
3735                int newScrollX = pinLocX(mScrollX + deltaX);
3736                deltaX = newScrollX - mScrollX;
3737                int newScrollY = pinLocY(mScrollY + deltaY);
3738                deltaY = newScrollY - mScrollY;
3739                boolean done = false;
3740                if (deltaX == 0 && deltaY == 0) {
3741                    done = true;
3742                } else {
3743                    if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_Y) {
3744                        int ax = Math.abs(deltaX);
3745                        int ay = Math.abs(deltaY);
3746                        if (mSnapScrollMode == SNAP_X) {
3747                            // radical change means getting out of snap mode
3748                            if (ay > MAX_SLOPE_FOR_DIAG * ax
3749                                    && ay > MIN_BREAK_SNAP_CROSS_DISTANCE) {
3750                                mSnapScrollMode = SNAP_NONE;
3751                            }
3752                            // reverse direction means lock in the snap mode
3753                            if ((ax > MAX_SLOPE_FOR_DIAG * ay) &&
3754                                    ((mSnapPositive &&
3755                                    deltaX < -mMinLockSnapReverseDistance)
3756                                    || (!mSnapPositive &&
3757                                    deltaX > mMinLockSnapReverseDistance))) {
3758                                mSnapScrollMode = SNAP_X_LOCK;
3759                            }
3760                        } else {
3761                            // radical change means getting out of snap mode
3762                            if ((ax > MAX_SLOPE_FOR_DIAG * ay)
3763                                    && ax > MIN_BREAK_SNAP_CROSS_DISTANCE) {
3764                                mSnapScrollMode = SNAP_NONE;
3765                            }
3766                            // reverse direction means lock in the snap mode
3767                            if ((ay > MAX_SLOPE_FOR_DIAG * ax) &&
3768                                    ((mSnapPositive &&
3769                                    deltaY < -mMinLockSnapReverseDistance)
3770                                    || (!mSnapPositive &&
3771                                    deltaY > mMinLockSnapReverseDistance))) {
3772                                mSnapScrollMode = SNAP_Y_LOCK;
3773                            }
3774                        }
3775                    }
3776
3777                    if (mSnapScrollMode == SNAP_X
3778                            || mSnapScrollMode == SNAP_X_LOCK) {
3779                        scrollBy(deltaX, 0);
3780                        mLastTouchX = x;
3781                    } else if (mSnapScrollMode == SNAP_Y
3782                            || mSnapScrollMode == SNAP_Y_LOCK) {
3783                        scrollBy(0, deltaY);
3784                        mLastTouchY = y;
3785                    } else {
3786                        scrollBy(deltaX, deltaY);
3787                        mLastTouchX = x;
3788                        mLastTouchY = y;
3789                    }
3790                    mLastTouchTime = eventTime;
3791                    mUserScroll = true;
3792                }
3793
3794                if (!getSettings().getBuiltInZoomControls()) {
3795                    boolean showPlusMinus = mMinZoomScale < mMaxZoomScale;
3796                    boolean showMagnify = canZoomScrollOut();
3797                    if (mZoomControls != null && (showPlusMinus || showMagnify)) {
3798                        if (mZoomControls.getVisibility() == View.VISIBLE) {
3799                            mPrivateHandler.removeCallbacks(mZoomControlRunnable);
3800                        } else {
3801                            mZoomControls.show(showPlusMinus, showMagnify);
3802                        }
3803                        mPrivateHandler.postDelayed(mZoomControlRunnable,
3804                                ZOOM_CONTROLS_TIMEOUT);
3805                    }
3806                }
3807
3808                if (done) {
3809                    // return false to indicate that we can't pan out of the
3810                    // view space
3811                    return false;
3812                }
3813                break;
3814            }
3815            case MotionEvent.ACTION_UP: {
3816                mLastTouchUpTime = eventTime;
3817                switch (mTouchMode) {
3818                    case TOUCH_INIT_MODE: // tap
3819                    case TOUCH_SHORTPRESS_START_MODE:
3820                    case TOUCH_SHORTPRESS_MODE:
3821                        mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
3822                        mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
3823                        mTouchMode = TOUCH_DONE_MODE;
3824                        doShortPress();
3825                        break;
3826                    case TOUCH_SELECT_MODE:
3827                        commitCopy();
3828                        mTouchSelection = false;
3829                        break;
3830                    case SCROLL_ZOOM_ANIMATION_IN:
3831                    case SCROLL_ZOOM_ANIMATION_OUT:
3832                        // no action during scroll animation
3833                        break;
3834                    case SCROLL_ZOOM_OUT:
3835                        if (DebugFlags.WEB_VIEW) {
3836                            Log.v(LOGTAG, "ACTION_UP SCROLL_ZOOM_OUT"
3837                                    + " eventTime - mLastTouchTime="
3838                                    + (eventTime - mLastTouchTime));
3839                        }
3840                        // for now, always zoom back when the drag completes
3841                        if (true || eventTime - mLastTouchTime < TAP_TIMEOUT) {
3842                            // but if we tap, zoom in where we tap
3843                            if (eventTime - mLastTouchTime < TAP_TIMEOUT) {
3844                                zoomScrollTap(x, y);
3845                            }
3846                            // start zooming in back to the original view
3847                            setZoomScrollIn();
3848                            mTouchMode = SCROLL_ZOOM_ANIMATION_IN;
3849                            invalidate();
3850                        }
3851                        break;
3852                    case TOUCH_DRAG_MODE:
3853                        // if the user waits a while w/o moving before the
3854                        // up, we don't want to do a fling
3855                        if (eventTime - mLastTouchTime <= MIN_FLING_TIME) {
3856                            mVelocityTracker.addMovement(ev);
3857                            doFling();
3858                            break;
3859                        }
3860                        WebViewCore.resumeUpdate(mWebViewCore);
3861                        break;
3862                    case TOUCH_DRAG_START_MODE:
3863                    case TOUCH_DONE_MODE:
3864                        // do nothing
3865                        break;
3866                }
3867                // we also use mVelocityTracker == null to tell us that we are
3868                // not "moving around", so we can take the slower/prettier
3869                // mode in the drawing code
3870                if (mVelocityTracker != null) {
3871                    mVelocityTracker.recycle();
3872                    mVelocityTracker = null;
3873                }
3874                break;
3875            }
3876            case MotionEvent.ACTION_CANCEL: {
3877                // we also use mVelocityTracker == null to tell us that we are
3878                // not "moving around", so we can take the slower/prettier
3879                // mode in the drawing code
3880                if (mVelocityTracker != null) {
3881                    mVelocityTracker.recycle();
3882                    mVelocityTracker = null;
3883                }
3884                if (mTouchMode == SCROLL_ZOOM_OUT ||
3885                        mTouchMode == SCROLL_ZOOM_ANIMATION_IN) {
3886                    scrollTo(mZoomScrollX, mZoomScrollY);
3887                } else if (mTouchMode == TOUCH_DRAG_MODE) {
3888                    WebViewCore.resumeUpdate(mWebViewCore);
3889                }
3890                mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
3891                mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
3892                mTouchMode = TOUCH_DONE_MODE;
3893                int contentX = viewToContent((int) mLastTouchX + mScrollX);
3894                int contentY = viewToContent((int) mLastTouchY + mScrollY);
3895                nativeClearFocus(contentX, contentY);
3896                break;
3897            }
3898        }
3899        return true;
3900    }
3901
3902    private long mTrackballFirstTime = 0;
3903    private long mTrackballLastTime = 0;
3904    private float mTrackballRemainsX = 0.0f;
3905    private float mTrackballRemainsY = 0.0f;
3906    private int mTrackballXMove = 0;
3907    private int mTrackballYMove = 0;
3908    private boolean mExtendSelection = false;
3909    private boolean mTouchSelection = false;
3910    private static final int TRACKBALL_KEY_TIMEOUT = 1000;
3911    private static final int TRACKBALL_TIMEOUT = 200;
3912    private static final int TRACKBALL_WAIT = 100;
3913    private static final int TRACKBALL_SCALE = 400;
3914    private static final int TRACKBALL_SCROLL_COUNT = 5;
3915    private static final int TRACKBALL_MOVE_COUNT = 10;
3916    private static final int TRACKBALL_MULTIPLIER = 3;
3917    private static final int SELECT_CURSOR_OFFSET = 16;
3918    private int mSelectX = 0;
3919    private int mSelectY = 0;
3920    private boolean mShiftIsPressed = false;
3921    private boolean mTrackballDown = false;
3922    private long mTrackballUpTime = 0;
3923    private long mLastFocusTime = 0;
3924    private Rect mLastFocusBounds;
3925
3926    // Set by default; BrowserActivity clears to interpret trackball data
3927    // directly for movement. Currently, the framework only passes
3928    // arrow key events, not trackball events, from one child to the next
3929    private boolean mMapTrackballToArrowKeys = true;
3930
3931    public void setMapTrackballToArrowKeys(boolean setMap) {
3932        mMapTrackballToArrowKeys = setMap;
3933    }
3934
3935    void resetTrackballTime() {
3936        mTrackballLastTime = 0;
3937    }
3938
3939    @Override
3940    public boolean onTrackballEvent(MotionEvent ev) {
3941        long time = ev.getEventTime();
3942        if ((ev.getMetaState() & KeyEvent.META_ALT_ON) != 0) {
3943            if (ev.getY() > 0) pageDown(true);
3944            if (ev.getY() < 0) pageUp(true);
3945            return true;
3946        }
3947        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
3948            mPrivateHandler.removeMessages(SWITCH_TO_CLICK);
3949            mTrackballDown = true;
3950            if (mNativeClass != 0) {
3951                nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true);
3952            }
3953            if (time - mLastFocusTime <= TRACKBALL_TIMEOUT
3954                    && !mLastFocusBounds.equals(nativeGetCursorRingBounds())) {
3955                nativeSelectBestAt(mLastFocusBounds);
3956            }
3957            if (DebugFlags.WEB_VIEW) {
3958                Log.v(LOGTAG, "onTrackballEvent down ev=" + ev
3959                        + " time=" + time
3960                        + " mLastFocusTime=" + mLastFocusTime);
3961            }
3962            if (isInTouchMode()) requestFocusFromTouch();
3963            return false; // let common code in onKeyDown at it
3964        }
3965        if (ev.getAction() == MotionEvent.ACTION_UP) {
3966            // LONG_PRESS_ENTER is set in common onKeyDown
3967            mPrivateHandler.removeMessages(LONG_PRESS_ENTER);
3968            mTrackballDown = false;
3969            mTrackballUpTime = time;
3970            if (mShiftIsPressed) {
3971                if (mExtendSelection) {
3972                    commitCopy();
3973                } else {
3974                    mExtendSelection = true;
3975                }
3976            }
3977            if (DebugFlags.WEB_VIEW) {
3978                Log.v(LOGTAG, "onTrackballEvent up ev=" + ev
3979                        + " time=" + time
3980                );
3981            }
3982            return false; // let common code in onKeyUp at it
3983        }
3984        if (mMapTrackballToArrowKeys && mShiftIsPressed == false) {
3985            if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent gmail quit");
3986            return false;
3987        }
3988        // no move if we're still waiting on SWITCH_TO_CLICK timeout
3989        if (mTouchMode == TOUCH_DOUBLECLICK_MODE) {
3990            if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent 2 click quit");
3991            return true;
3992        }
3993        if (mTrackballDown) {
3994            if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent down quit");
3995            return true; // discard move if trackball is down
3996        }
3997        if (time - mTrackballUpTime < TRACKBALL_TIMEOUT) {
3998            if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent up timeout quit");
3999            return true;
4000        }
4001        // TODO: alternatively we can do panning as touch does
4002        switchOutDrawHistory();
4003        if (time - mTrackballLastTime > TRACKBALL_TIMEOUT) {
4004            if (DebugFlags.WEB_VIEW) {
4005                Log.v(LOGTAG, "onTrackballEvent time="
4006                        + time + " last=" + mTrackballLastTime);
4007            }
4008            mTrackballFirstTime = time;
4009            mTrackballXMove = mTrackballYMove = 0;
4010        }
4011        mTrackballLastTime = time;
4012        if (DebugFlags.WEB_VIEW) {
4013            Log.v(LOGTAG, "onTrackballEvent ev=" + ev + " time=" + time);
4014        }
4015        mTrackballRemainsX += ev.getX();
4016        mTrackballRemainsY += ev.getY();
4017        doTrackball(time);
4018        return true;
4019    }
4020
4021    void moveSelection(float xRate, float yRate) {
4022        if (mNativeClass == 0)
4023            return;
4024        int width = getViewWidth();
4025        int height = getViewHeight();
4026        mSelectX += scaleTrackballX(xRate, width);
4027        mSelectY += scaleTrackballY(yRate, height);
4028        int maxX = width + mScrollX;
4029        int maxY = height + mScrollY;
4030        mSelectX = Math.min(maxX, Math.max(mScrollX - SELECT_CURSOR_OFFSET
4031                , mSelectX));
4032        mSelectY = Math.min(maxY, Math.max(mScrollY - SELECT_CURSOR_OFFSET
4033                , mSelectY));
4034        if (DebugFlags.WEB_VIEW) {
4035            Log.v(LOGTAG, "moveSelection"
4036                    + " mSelectX=" + mSelectX
4037                    + " mSelectY=" + mSelectY
4038                    + " mScrollX=" + mScrollX
4039                    + " mScrollY=" + mScrollY
4040                    + " xRate=" + xRate
4041                    + " yRate=" + yRate
4042                    );
4043        }
4044        nativeMoveSelection(viewToContent(mSelectX)
4045                , viewToContent(mSelectY), mExtendSelection);
4046        int scrollX = mSelectX < mScrollX ? -SELECT_CURSOR_OFFSET
4047                : mSelectX > maxX - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET
4048                : 0;
4049        int scrollY = mSelectY < mScrollY ? -SELECT_CURSOR_OFFSET
4050                : mSelectY > maxY - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET
4051                : 0;
4052        pinScrollBy(scrollX, scrollY, true, 0);
4053        Rect select = new Rect(mSelectX, mSelectY, mSelectX + 1, mSelectY + 1);
4054        requestRectangleOnScreen(select);
4055        invalidate();
4056   }
4057
4058    private int scaleTrackballX(float xRate, int width) {
4059        int xMove = (int) (xRate / TRACKBALL_SCALE * width);
4060        int nextXMove = xMove;
4061        if (xMove > 0) {
4062            if (xMove > mTrackballXMove) {
4063                xMove -= mTrackballXMove;
4064            }
4065        } else if (xMove < mTrackballXMove) {
4066            xMove -= mTrackballXMove;
4067        }
4068        mTrackballXMove = nextXMove;
4069        return xMove;
4070    }
4071
4072    private int scaleTrackballY(float yRate, int height) {
4073        int yMove = (int) (yRate / TRACKBALL_SCALE * height);
4074        int nextYMove = yMove;
4075        if (yMove > 0) {
4076            if (yMove > mTrackballYMove) {
4077                yMove -= mTrackballYMove;
4078            }
4079        } else if (yMove < mTrackballYMove) {
4080            yMove -= mTrackballYMove;
4081        }
4082        mTrackballYMove = nextYMove;
4083        return yMove;
4084    }
4085
4086    private int keyCodeToSoundsEffect(int keyCode) {
4087        switch(keyCode) {
4088            case KeyEvent.KEYCODE_DPAD_UP:
4089                return SoundEffectConstants.NAVIGATION_UP;
4090            case KeyEvent.KEYCODE_DPAD_RIGHT:
4091                return SoundEffectConstants.NAVIGATION_RIGHT;
4092            case KeyEvent.KEYCODE_DPAD_DOWN:
4093                return SoundEffectConstants.NAVIGATION_DOWN;
4094            case KeyEvent.KEYCODE_DPAD_LEFT:
4095                return SoundEffectConstants.NAVIGATION_LEFT;
4096        }
4097        throw new IllegalArgumentException("keyCode must be one of " +
4098                "{KEYCODE_DPAD_UP, KEYCODE_DPAD_RIGHT, KEYCODE_DPAD_DOWN, " +
4099                "KEYCODE_DPAD_LEFT}.");
4100    }
4101
4102    private void doTrackball(long time) {
4103        int elapsed = (int) (mTrackballLastTime - mTrackballFirstTime);
4104        if (elapsed == 0) {
4105            elapsed = TRACKBALL_TIMEOUT;
4106        }
4107        float xRate = mTrackballRemainsX * 1000 / elapsed;
4108        float yRate = mTrackballRemainsY * 1000 / elapsed;
4109        if (mShiftIsPressed) {
4110            moveSelection(xRate, yRate);
4111            mTrackballRemainsX = mTrackballRemainsY = 0;
4112            return;
4113        }
4114        float ax = Math.abs(xRate);
4115        float ay = Math.abs(yRate);
4116        float maxA = Math.max(ax, ay);
4117        if (DebugFlags.WEB_VIEW) {
4118            Log.v(LOGTAG, "doTrackball elapsed=" + elapsed
4119                    + " xRate=" + xRate
4120                    + " yRate=" + yRate
4121                    + " mTrackballRemainsX=" + mTrackballRemainsX
4122                    + " mTrackballRemainsY=" + mTrackballRemainsY);
4123        }
4124        int width = mContentWidth - getViewWidth();
4125        int height = mContentHeight - getViewHeight();
4126        if (width < 0) width = 0;
4127        if (height < 0) height = 0;
4128        if (mTouchMode == SCROLL_ZOOM_OUT) {
4129            int oldX = mZoomScrollX;
4130            int oldY = mZoomScrollY;
4131            int maxWH = Math.max(width, height);
4132            mZoomScrollX += scaleTrackballX(xRate, maxWH);
4133            mZoomScrollY += scaleTrackballY(yRate, maxWH);
4134            if (DebugFlags.WEB_VIEW) {
4135                Log.v(LOGTAG, "doTrackball SCROLL_ZOOM_OUT"
4136                        + " mZoomScrollX=" + mZoomScrollX
4137                        + " mZoomScrollY=" + mZoomScrollY);
4138            }
4139            mZoomScrollX = Math.min(width, Math.max(0, mZoomScrollX));
4140            mZoomScrollY = Math.min(height, Math.max(0, mZoomScrollY));
4141            if (oldX != mZoomScrollX || oldY != mZoomScrollY) {
4142                invalidate();
4143            }
4144            mTrackballRemainsX = mTrackballRemainsY = 0;
4145            return;
4146        }
4147        ax = Math.abs(mTrackballRemainsX * TRACKBALL_MULTIPLIER);
4148        ay = Math.abs(mTrackballRemainsY * TRACKBALL_MULTIPLIER);
4149        maxA = Math.max(ax, ay);
4150        int count = Math.max(0, (int) maxA);
4151        int oldScrollX = mScrollX;
4152        int oldScrollY = mScrollY;
4153        if (count > 0) {
4154            int selectKeyCode = ax < ay ? mTrackballRemainsY < 0 ?
4155                    KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_DPAD_DOWN :
4156                    mTrackballRemainsX < 0 ? KeyEvent.KEYCODE_DPAD_LEFT :
4157                    KeyEvent.KEYCODE_DPAD_RIGHT;
4158            count = Math.min(count, TRACKBALL_MOVE_COUNT);
4159            if (DebugFlags.WEB_VIEW) {
4160                Log.v(LOGTAG, "doTrackball keyCode=" + selectKeyCode
4161                        + " count=" + count
4162                        + " mTrackballRemainsX=" + mTrackballRemainsX
4163                        + " mTrackballRemainsY=" + mTrackballRemainsY);
4164            }
4165            if (navHandledKey(selectKeyCode, count, false, time)) {
4166                playSoundEffect(keyCodeToSoundsEffect(selectKeyCode));
4167            }
4168            mTrackballRemainsX = mTrackballRemainsY = 0;
4169        }
4170        if (count >= TRACKBALL_SCROLL_COUNT) {
4171            int xMove = scaleTrackballX(xRate, width);
4172            int yMove = scaleTrackballY(yRate, height);
4173            if (DebugFlags.WEB_VIEW) {
4174                Log.v(LOGTAG, "doTrackball pinScrollBy"
4175                        + " count=" + count
4176                        + " xMove=" + xMove + " yMove=" + yMove
4177                        + " mScrollX-oldScrollX=" + (mScrollX-oldScrollX)
4178                        + " mScrollY-oldScrollY=" + (mScrollY-oldScrollY)
4179                        );
4180            }
4181            if (Math.abs(mScrollX - oldScrollX) > Math.abs(xMove)) {
4182                xMove = 0;
4183            }
4184            if (Math.abs(mScrollY - oldScrollY) > Math.abs(yMove)) {
4185                yMove = 0;
4186            }
4187            if (xMove != 0 || yMove != 0) {
4188                pinScrollBy(xMove, yMove, true, 0);
4189            }
4190            mUserScroll = true;
4191        }
4192        mWebViewCore.sendMessage(EventHub.UNBLOCK_FOCUS);
4193    }
4194
4195    public void flingScroll(int vx, int vy) {
4196        int maxX = Math.max(computeHorizontalScrollRange() - getViewWidth(), 0);
4197        int maxY = Math.max(computeVerticalScrollRange() - getViewHeight(), 0);
4198
4199        mScroller.fling(mScrollX, mScrollY, vx, vy, 0, maxX, 0, maxY);
4200        invalidate();
4201    }
4202
4203    private void doFling() {
4204        if (mVelocityTracker == null) {
4205            return;
4206        }
4207        int maxX = Math.max(computeHorizontalScrollRange() - getViewWidth(), 0);
4208        int maxY = Math.max(computeVerticalScrollRange() - getViewHeight(), 0);
4209
4210        mVelocityTracker.computeCurrentVelocity(1000);
4211        int vx = (int) mVelocityTracker.getXVelocity();
4212        int vy = (int) mVelocityTracker.getYVelocity();
4213
4214        if (mSnapScrollMode != SNAP_NONE) {
4215            if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_X_LOCK) {
4216                vy = 0;
4217            } else {
4218                vx = 0;
4219            }
4220        }
4221
4222        if (true /* EMG release: make our fling more like Maps' */) {
4223            // maps cuts their velocity in half
4224            vx = vx * 3 / 4;
4225            vy = vy * 3 / 4;
4226        }
4227
4228        mScroller.fling(mScrollX, mScrollY, -vx, -vy, 0, maxX, 0, maxY);
4229        // TODO: duration is calculated based on velocity, if the range is
4230        // small, the animation will stop before duration is up. We may
4231        // want to calculate how long the animation is going to run to precisely
4232        // resume the webcore update.
4233        final int time = mScroller.getDuration();
4234        mPrivateHandler.sendEmptyMessageDelayed(RESUME_WEBCORE_UPDATE, time);
4235        invalidate();
4236    }
4237
4238    private boolean zoomWithPreview(float scale) {
4239        float oldScale = mActualScale;
4240
4241        // snap to 100% if it is close
4242        if (scale > 0.95f && scale < 1.05f) {
4243            scale = 1.0f;
4244        }
4245
4246        setNewZoomScale(scale, false);
4247
4248        if (oldScale != mActualScale) {
4249            // use mZoomPickerScale to see zoom preview first
4250            mZoomStart = SystemClock.uptimeMillis();
4251            mInvInitialZoomScale = 1.0f / oldScale;
4252            mInvFinalZoomScale = 1.0f / mActualScale;
4253            mZoomScale = mActualScale;
4254            invalidate();
4255            return true;
4256        } else {
4257            return false;
4258        }
4259    }
4260
4261    /**
4262     * Returns a view containing zoom controls i.e. +/- buttons. The caller is
4263     * in charge of installing this view to the view hierarchy. This view will
4264     * become visible when the user starts scrolling via touch and fade away if
4265     * the user does not interact with it.
4266     * <p/>
4267     * API version 3 introduces a built-in zoom mechanism that is shown
4268     * automatically by the MapView. This is the preferred approach for
4269     * showing the zoom UI.
4270     *
4271     * @deprecated The built-in zoom mechanism is preferred, see
4272     *             {@link WebSettings#setBuiltInZoomControls(boolean)}.
4273     */
4274    @Deprecated
4275    public View getZoomControls() {
4276        if (!getSettings().supportZoom()) {
4277            Log.w(LOGTAG, "This WebView doesn't support zoom.");
4278            return null;
4279        }
4280        if (mZoomControls == null) {
4281            mZoomControls = createZoomControls();
4282
4283            /*
4284             * need to be set to VISIBLE first so that getMeasuredHeight() in
4285             * {@link #onSizeChanged()} can return the measured value for proper
4286             * layout.
4287             */
4288            mZoomControls.setVisibility(View.VISIBLE);
4289            mZoomControlRunnable = new Runnable() {
4290                public void run() {
4291
4292                    /* Don't dismiss the controls if the user has
4293                     * focus on them. Wait and check again later.
4294                     */
4295                    if (!mZoomControls.hasFocus()) {
4296                        mZoomControls.hide();
4297                    } else {
4298                        mPrivateHandler.removeCallbacks(mZoomControlRunnable);
4299                        mPrivateHandler.postDelayed(mZoomControlRunnable,
4300                                ZOOM_CONTROLS_TIMEOUT);
4301                    }
4302                }
4303            };
4304        }
4305        return mZoomControls;
4306    }
4307
4308    private ExtendedZoomControls createZoomControls() {
4309        ExtendedZoomControls zoomControls = new ExtendedZoomControls(mContext
4310            , null);
4311        zoomControls.setOnZoomInClickListener(new OnClickListener() {
4312            public void onClick(View v) {
4313                // reset time out
4314                mPrivateHandler.removeCallbacks(mZoomControlRunnable);
4315                mPrivateHandler.postDelayed(mZoomControlRunnable,
4316                        ZOOM_CONTROLS_TIMEOUT);
4317                zoomIn();
4318            }
4319        });
4320        zoomControls.setOnZoomOutClickListener(new OnClickListener() {
4321            public void onClick(View v) {
4322                // reset time out
4323                mPrivateHandler.removeCallbacks(mZoomControlRunnable);
4324                mPrivateHandler.postDelayed(mZoomControlRunnable,
4325                        ZOOM_CONTROLS_TIMEOUT);
4326                zoomOut();
4327            }
4328        });
4329        zoomControls.setOnZoomMagnifyClickListener(new OnClickListener() {
4330            public void onClick(View v) {
4331                mPrivateHandler.removeCallbacks(mZoomControlRunnable);
4332                mPrivateHandler.postDelayed(mZoomControlRunnable,
4333                        ZOOM_CONTROLS_TIMEOUT);
4334                zoomScrollOut();
4335            }
4336        });
4337        return zoomControls;
4338    }
4339
4340    /**
4341     * Gets the {@link ZoomButtonsController} which can be used to add
4342     * additional buttons to the zoom controls window.
4343     *
4344     * @return The instance of {@link ZoomButtonsController} used by this class,
4345     *         or null if it is unavailable.
4346     * @hide
4347     */
4348    public ZoomButtonsController getZoomButtonsController() {
4349        return mZoomButtonsController;
4350    }
4351
4352    /**
4353     * Perform zoom in in the webview
4354     * @return TRUE if zoom in succeeds. FALSE if no zoom changes.
4355     */
4356    public boolean zoomIn() {
4357        // TODO: alternatively we can disallow this during draw history mode
4358        switchOutDrawHistory();
4359        return zoomWithPreview(mActualScale * 1.25f);
4360    }
4361
4362    /**
4363     * Perform zoom out in the webview
4364     * @return TRUE if zoom out succeeds. FALSE if no zoom changes.
4365     */
4366    public boolean zoomOut() {
4367        // TODO: alternatively we can disallow this during draw history mode
4368        switchOutDrawHistory();
4369        return zoomWithPreview(mActualScale * 0.8f);
4370    }
4371
4372    private void updateSelection() {
4373        if (mNativeClass == 0) {
4374            return;
4375        }
4376        // mLastTouchX and mLastTouchY are the point in the current viewport
4377        int contentX = viewToContent((int) mLastTouchX + mScrollX);
4378        int contentY = viewToContent((int) mLastTouchY + mScrollY);
4379        Rect rect = new Rect(contentX - mNavSlop, contentY - mNavSlop,
4380                contentX + mNavSlop, contentY + mNavSlop);
4381        nativeSelectBestAt(rect);
4382    }
4383
4384    /*package*/ void shortPressOnTextField() {
4385        if (inEditingMode()) {
4386            View v = mWebTextView;
4387            int x = viewToContent((v.getLeft() + v.getRight()) >> 1);
4388            int y = viewToContent((v.getTop() + v.getBottom()) >> 1);
4389            nativeMotionUp(x, y, mNavSlop);
4390        }
4391    }
4392
4393    private void doShortPress() {
4394        if (mNativeClass == 0) {
4395            return;
4396        }
4397        switchOutDrawHistory();
4398        // mLastTouchX and mLastTouchY are the point in the current viewport
4399        int contentX = viewToContent((int) mLastTouchX + mScrollX);
4400        int contentY = viewToContent((int) mLastTouchY + mScrollY);
4401        if (nativeMotionUp(contentX, contentY, mNavSlop)) {
4402            if (mLogEvent) {
4403                Checkin.updateStats(mContext.getContentResolver(),
4404                        Checkin.Stats.Tag.BROWSER_SNAP_CENTER, 1, 0.0);
4405            }
4406        }
4407        if (nativeHasCursorNode() && !nativeCursorIsTextInput()) {
4408            playSoundEffect(SoundEffectConstants.CLICK);
4409        }
4410    }
4411
4412    // Called by JNI to handle a touch on a node representing an email address,
4413    // address, or phone number
4414    private void overrideLoading(String url) {
4415        mCallbackProxy.uiOverrideUrlLoading(url);
4416    }
4417
4418    @Override
4419    public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
4420        boolean result = false;
4421        if (inEditingMode()) {
4422            result = mWebTextView.requestFocus(direction,
4423                    previouslyFocusedRect);
4424        } else {
4425            result = super.requestFocus(direction, previouslyFocusedRect);
4426            if (mWebViewCore.getSettings().getNeedInitialFocus()) {
4427                // For cases such as GMail, where we gain focus from a direction,
4428                // we want to move to the first available link.
4429                // FIXME: If there are no visible links, we may not want to
4430                int fakeKeyDirection = 0;
4431                switch(direction) {
4432                    case View.FOCUS_UP:
4433                        fakeKeyDirection = KeyEvent.KEYCODE_DPAD_UP;
4434                        break;
4435                    case View.FOCUS_DOWN:
4436                        fakeKeyDirection = KeyEvent.KEYCODE_DPAD_DOWN;
4437                        break;
4438                    case View.FOCUS_LEFT:
4439                        fakeKeyDirection = KeyEvent.KEYCODE_DPAD_LEFT;
4440                        break;
4441                    case View.FOCUS_RIGHT:
4442                        fakeKeyDirection = KeyEvent.KEYCODE_DPAD_RIGHT;
4443                        break;
4444                    default:
4445                        return result;
4446                }
4447                if (mNativeClass != 0 && !nativeHasCursorNode()) {
4448                    navHandledKey(fakeKeyDirection, 1, true, 0);
4449                }
4450            }
4451        }
4452        return result;
4453    }
4454
4455    @Override
4456    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
4457        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
4458
4459        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
4460        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
4461        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
4462        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
4463
4464        int measuredHeight = heightSize;
4465        int measuredWidth = widthSize;
4466
4467        // Grab the content size from WebViewCore.
4468        int contentHeight = mContentHeight;
4469        int contentWidth = mContentWidth;
4470
4471//        Log.d(LOGTAG, "------- measure " + heightMode);
4472
4473        if (heightMode != MeasureSpec.EXACTLY) {
4474            mHeightCanMeasure = true;
4475            measuredHeight = contentHeight;
4476            if (heightMode == MeasureSpec.AT_MOST) {
4477                // If we are larger than the AT_MOST height, then our height can
4478                // no longer be measured and we should scroll internally.
4479                if (measuredHeight > heightSize) {
4480                    measuredHeight = heightSize;
4481                    mHeightCanMeasure = false;
4482                }
4483            }
4484        } else {
4485            mHeightCanMeasure = false;
4486        }
4487        if (mNativeClass != 0) {
4488            nativeSetHeightCanMeasure(mHeightCanMeasure);
4489        }
4490        // For the width, always use the given size unless unspecified.
4491        if (widthMode == MeasureSpec.UNSPECIFIED) {
4492            mWidthCanMeasure = true;
4493            measuredWidth = contentWidth;
4494        } else {
4495            mWidthCanMeasure = false;
4496        }
4497
4498        synchronized (this) {
4499            setMeasuredDimension(measuredWidth, measuredHeight);
4500        }
4501    }
4502
4503    @Override
4504    public boolean requestChildRectangleOnScreen(View child,
4505                                                 Rect rect,
4506                                                 boolean immediate) {
4507        rect.offset(child.getLeft() - child.getScrollX(),
4508                child.getTop() - child.getScrollY());
4509
4510        int height = getHeight() - getHorizontalScrollbarHeight();
4511        int screenTop = mScrollY;
4512        int screenBottom = screenTop + height;
4513
4514        int scrollYDelta = 0;
4515
4516        if (rect.bottom > screenBottom) {
4517            int oneThirdOfScreenHeight = height / 3;
4518            if (rect.height() > 2 * oneThirdOfScreenHeight) {
4519                // If the rectangle is too tall to fit in the bottom two thirds
4520                // of the screen, place it at the top.
4521                scrollYDelta = rect.top - screenTop;
4522            } else {
4523                // If the rectangle will still fit on screen, we want its
4524                // top to be in the top third of the screen.
4525                scrollYDelta = rect.top - (screenTop + oneThirdOfScreenHeight);
4526            }
4527        } else if (rect.top < screenTop) {
4528            scrollYDelta = rect.top - screenTop;
4529        }
4530
4531        int width = getWidth() - getVerticalScrollbarWidth();
4532        int screenLeft = mScrollX;
4533        int screenRight = screenLeft + width;
4534
4535        int scrollXDelta = 0;
4536
4537        if (rect.right > screenRight && rect.left > screenLeft) {
4538            if (rect.width() > width) {
4539                scrollXDelta += (rect.left - screenLeft);
4540            } else {
4541                scrollXDelta += (rect.right - screenRight);
4542            }
4543        } else if (rect.left < screenLeft) {
4544            scrollXDelta -= (screenLeft - rect.left);
4545        }
4546
4547        if ((scrollYDelta | scrollXDelta) != 0) {
4548            return pinScrollBy(scrollXDelta, scrollYDelta, !immediate, 0);
4549        }
4550
4551        return false;
4552    }
4553
4554    /* package */ void replaceTextfieldText(int oldStart, int oldEnd,
4555            String replace, int newStart, int newEnd) {
4556        HashMap arg = new HashMap();
4557        arg.put("replace", replace);
4558        arg.put("start", Integer.valueOf(newStart));
4559        arg.put("end", Integer.valueOf(newEnd));
4560        mTextGeneration++;
4561        mWebViewCore.sendMessage(EventHub.REPLACE_TEXT, oldStart, oldEnd, arg);
4562    }
4563
4564    /* package */ void passToJavaScript(String currentText, KeyEvent event) {
4565        HashMap arg = new HashMap();
4566        arg.put("event", event);
4567        arg.put("currentText", currentText);
4568        // Increase our text generation number, and pass it to webcore thread
4569        mTextGeneration++;
4570        mWebViewCore.sendMessage(EventHub.PASS_TO_JS, mTextGeneration, 0, arg);
4571        // WebKit's document state is not saved until about to leave the page.
4572        // To make sure the host application, like Browser, has the up to date
4573        // document state when it goes to background, we force to save the
4574        // document state.
4575        mWebViewCore.removeMessages(EventHub.SAVE_DOCUMENT_STATE);
4576        mWebViewCore.sendMessageDelayed(EventHub.SAVE_DOCUMENT_STATE,
4577                cursorData(), 1000);
4578    }
4579
4580    /* package */ WebViewCore getWebViewCore() {
4581        return mWebViewCore;
4582    }
4583
4584    //-------------------------------------------------------------------------
4585    // Methods can be called from a separate thread, like WebViewCore
4586    // If it needs to call the View system, it has to send message.
4587    //-------------------------------------------------------------------------
4588
4589    /**
4590     * General handler to receive message coming from webkit thread
4591     */
4592    class PrivateHandler extends Handler {
4593        @Override
4594        public void handleMessage(Message msg) {
4595            if (DebugFlags.WEB_VIEW) {
4596                Log.v(LOGTAG, msg.what < REMEMBER_PASSWORD || msg.what
4597                        > INVAL_RECT_MSG_ID ? Integer.toString(msg.what)
4598                        : HandlerDebugString[msg.what - REMEMBER_PASSWORD]);
4599            }
4600            switch (msg.what) {
4601                case REMEMBER_PASSWORD: {
4602                    mDatabase.setUsernamePassword(
4603                            msg.getData().getString("host"),
4604                            msg.getData().getString("username"),
4605                            msg.getData().getString("password"));
4606                    ((Message) msg.obj).sendToTarget();
4607                    break;
4608                }
4609                case NEVER_REMEMBER_PASSWORD: {
4610                    mDatabase.setUsernamePassword(
4611                            msg.getData().getString("host"), null, null);
4612                    ((Message) msg.obj).sendToTarget();
4613                    break;
4614                }
4615                case SWITCH_TO_SHORTPRESS: {
4616                    if (mTouchMode == TOUCH_INIT_MODE) {
4617                        mTouchMode = TOUCH_SHORTPRESS_START_MODE;
4618                        updateSelection();
4619                    }
4620                    break;
4621                }
4622                case SWITCH_TO_LONGPRESS: {
4623                    mTouchMode = TOUCH_DONE_MODE;
4624                    performLongClick();
4625                    rebuildWebTextView();
4626                    break;
4627                }
4628                case SWITCH_TO_CLICK:
4629                    mTouchMode = TOUCH_DONE_MODE;
4630                    Rect visibleRect = sendOurVisibleRect();
4631                    // Note that sendOurVisibleRect calls viewToContent, so the
4632                    // coordinates should be in content coordinates.
4633                    if (!nativeCursorIntersects(visibleRect)) {
4634                        break;
4635                    }
4636                    nativeSetFollowedLink(true);
4637                    mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE,
4638                            cursorData());
4639                    playSoundEffect(SoundEffectConstants.CLICK);
4640                    if (!mCallbackProxy.uiOverrideUrlLoading(nativeCursorText())) {
4641                        mWebViewCore.sendMessage(EventHub.CLICK);
4642                    }
4643                    break;
4644                case SCROLL_BY_MSG_ID:
4645                    setContentScrollBy(msg.arg1, msg.arg2, (Boolean) msg.obj);
4646                    break;
4647                case SYNC_SCROLL_TO_MSG_ID:
4648                    if (mUserScroll) {
4649                        // if user has scrolled explicitly, don't sync the
4650                        // scroll position any more
4651                        mUserScroll = false;
4652                        break;
4653                    }
4654                    // fall through
4655                case SCROLL_TO_MSG_ID:
4656                    if (setContentScrollTo(msg.arg1, msg.arg2)) {
4657                        // if we can't scroll to the exact position due to pin,
4658                        // send a message to WebCore to re-scroll when we get a
4659                        // new picture
4660                        mUserScroll = false;
4661                        mWebViewCore.sendMessage(EventHub.SYNC_SCROLL,
4662                                msg.arg1, msg.arg2);
4663                    }
4664                    break;
4665                case SPAWN_SCROLL_TO_MSG_ID:
4666                    spawnContentScrollTo(msg.arg1, msg.arg2);
4667                    break;
4668                case NEW_PICTURE_MSG_ID:
4669                    // called for new content
4670                    final WebViewCore.DrawData draw =
4671                            (WebViewCore.DrawData) msg.obj;
4672                    final Point viewSize = draw.mViewPoint;
4673                    if (mZoomScale > 0) {
4674                        // use the same logic in sendViewSizeZoom() to make sure
4675                        // the mZoomScale has matched the viewSize so that we
4676                        // can clear mZoomScale
4677                        if (Math.round(getViewWidth() / mZoomScale) == viewSize.x) {
4678                            mZoomScale = 0;
4679                            mWebViewCore.sendMessage(EventHub.SET_SNAP_ANCHOR,
4680                                    0, 0);
4681                        }
4682                    }
4683                    if (!mMinZoomScaleFixed) {
4684                        mMinZoomScale = (float) getViewWidth()
4685                                / Math.max(ZOOM_OUT_WIDTH, draw.mWidthHeight.x);
4686                    }
4687                    // We update the layout (i.e. request a layout from the
4688                    // view system) if the last view size that we sent to
4689                    // WebCore matches the view size of the picture we just
4690                    // received in the fixed dimension.
4691                    final boolean updateLayout = viewSize.x == mLastWidthSent
4692                            && viewSize.y == mLastHeightSent;
4693                    recordNewContentSize(draw.mWidthHeight.x,
4694                            draw.mWidthHeight.y, updateLayout);
4695                    if (DebugFlags.WEB_VIEW) {
4696                        Rect b = draw.mInvalRegion.getBounds();
4697                        Log.v(LOGTAG, "NEW_PICTURE_MSG_ID {" +
4698                                b.left+","+b.top+","+b.right+","+b.bottom+"}");
4699                    }
4700                    invalidate(contentToView(draw.mInvalRegion.getBounds()));
4701                    if (mPictureListener != null) {
4702                        mPictureListener.onNewPicture(WebView.this, capturePicture());
4703                    }
4704                    break;
4705                case WEBCORE_INITIALIZED_MSG_ID:
4706                    // nativeCreate sets mNativeClass to a non-zero value
4707                    nativeCreate(msg.arg1);
4708                    break;
4709                case UPDATE_TEXTFIELD_TEXT_MSG_ID:
4710                    // Make sure that the textfield is currently focused
4711                    // and representing the same node as the pointer.
4712                    if (inEditingMode() &&
4713                            mWebTextView.isSameTextField(msg.arg1)) {
4714                        if (msg.getData().getBoolean("password")) {
4715                            Spannable text = (Spannable) mWebTextView.getText();
4716                            int start = Selection.getSelectionStart(text);
4717                            int end = Selection.getSelectionEnd(text);
4718                            mWebTextView.setInPassword(true);
4719                            // Restore the selection, which may have been
4720                            // ruined by setInPassword.
4721                            Spannable pword =
4722                                    (Spannable) mWebTextView.getText();
4723                            Selection.setSelection(pword, start, end);
4724                        // If the text entry has created more events, ignore
4725                        // this one.
4726                        } else if (msg.arg2 == mTextGeneration) {
4727                            mWebTextView.setTextAndKeepSelection(
4728                                    (String) msg.obj);
4729                        }
4730                    }
4731                    break;
4732                case DID_FIRST_LAYOUT_MSG_ID:
4733                    if (mNativeClass == 0) {
4734                        break;
4735                    }
4736// Do not reset the focus or clear the text; the user may have already
4737// navigated or entered text at this point. The focus should have gotten
4738// reset, if need be, when the focus cache was built. Similarly, the text
4739// view should already be torn down and rebuilt if needed.
4740//                    nativeResetFocus();
4741//                    clearTextEntry();
4742                    HashMap scaleLimit = (HashMap) msg.obj;
4743                    int minScale = (Integer) scaleLimit.get("minScale");
4744                    if (minScale == 0) {
4745                        mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE;
4746                        mMinZoomScaleFixed = false;
4747                    } else {
4748                        mMinZoomScale = (float) (minScale / 100.0);
4749                        mMinZoomScaleFixed = true;
4750                    }
4751                    int maxScale = (Integer) scaleLimit.get("maxScale");
4752                    if (maxScale == 0) {
4753                        mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE;
4754                    } else {
4755                        mMaxZoomScale = (float) (maxScale / 100.0);
4756                    }
4757                    // If history Picture is drawn, don't update zoomWidth
4758                    if (mDrawHistory) {
4759                        break;
4760                    }
4761                    int width = getViewWidth();
4762                    if (width == 0) {
4763                        break;
4764                    }
4765                    int initialScale = msg.arg1;
4766                    int viewportWidth = msg.arg2;
4767                    // by default starting a new page with 100% zoom scale.
4768                    float scale = 1.0f;
4769                    if (mInitialScale > 0) {
4770                        scale = mInitialScale / 100.0f;
4771                    } else  {
4772                        if (initialScale < 0) break;
4773                        if (mWebViewCore.getSettings().getUseWideViewPort()) {
4774                            // force viewSizeChanged by setting mLastWidthSent
4775                            // to 0
4776                            mLastWidthSent = 0;
4777                        }
4778                        if (initialScale == 0) {
4779                            // if viewportWidth is defined and it is smaller
4780                            // than the view width, zoom in to fill the view
4781                            if (viewportWidth > 0 && viewportWidth < width) {
4782                                scale = (float) width / viewportWidth;
4783                            }
4784                        } else {
4785                            scale = initialScale / 100.0f;
4786                        }
4787                    }
4788                    setNewZoomScale(scale, false);
4789                    break;
4790                case MARK_NODE_INVALID_ID:
4791                    nativeMarkNodeInvalid(msg.arg1);
4792                    break;
4793                case UPDATE_TEXT_ENTRY_MSG_ID:
4794                    // this is sent after finishing resize in WebViewCore. Make
4795                    // sure the text edit box is still on the  screen.
4796                    if (inEditingMode() && nativeCursorIsTextInput()) {
4797                        mWebTextView.bringIntoView();
4798                    }
4799                    rebuildWebTextView();
4800                    break;
4801                case RECOMPUTE_FOCUS_MSG_ID:
4802                    if (mNativeClass != 0) {
4803                        nativeRecomputeFocus();
4804                    }
4805                    break;
4806                case INVAL_RECT_MSG_ID: {
4807                    Rect r = (Rect)msg.obj;
4808                    if (r == null) {
4809                        invalidate();
4810                    } else {
4811                        // we need to scale r from content into view coords,
4812                        // which viewInvalidate() does for us
4813                        viewInvalidate(r.left, r.top, r.right, r.bottom);
4814                    }
4815                    break;
4816                }
4817                case REQUEST_FORM_DATA:
4818                    HashMap data = (HashMap) msg.obj;
4819                    if (mWebTextView.isSameTextField(msg.arg1)) {
4820                        AutoCompleteAdapter adapter =
4821                                (AutoCompleteAdapter) data.get("adapter");
4822                        mWebTextView.setAdapterCustom(adapter);
4823                    }
4824                    break;
4825                case UPDATE_CLIPBOARD:
4826                    String str = (String) msg.obj;
4827                    if (DebugFlags.WEB_VIEW) {
4828                        Log.v(LOGTAG, "UPDATE_CLIPBOARD " + str);
4829                    }
4830                    try {
4831                        IClipboard clip = IClipboard.Stub.asInterface(
4832                                ServiceManager.getService("clipboard"));
4833                                clip.setClipboardText(str);
4834                    } catch (android.os.RemoteException e) {
4835                        Log.e(LOGTAG, "Clipboard failed", e);
4836                    }
4837                    break;
4838                case RESUME_WEBCORE_UPDATE:
4839                    WebViewCore.resumeUpdate(mWebViewCore);
4840                    break;
4841
4842                case LONG_PRESS_ENTER:
4843                    // as this is shared by keydown and trackballdown, reset all
4844                    // the states
4845                    mGotEnterDown = false;
4846                    mTrackballDown = false;
4847                    // LONG_PRESS_ENTER is sent as a delayed message. If we
4848                    // switch to windows overview, the WebView will be
4849                    // temporarily removed from the view system. In that case,
4850                    // do nothing.
4851                    if (getParent() != null) {
4852                        performLongClick();
4853                    }
4854                    break;
4855
4856                case WEBCORE_NEED_TOUCH_EVENTS:
4857                    mForwardTouchEvents = (msg.arg1 != 0);
4858                    break;
4859
4860                case PREVENT_TOUCH_ID:
4861                    if (msg.arg1 == MotionEvent.ACTION_DOWN) {
4862                        mPreventDrag = msg.arg2 == 1;
4863                        if (mPreventDrag) {
4864                            mTouchMode = TOUCH_DONE_MODE;
4865                        }
4866                    }
4867                    break;
4868
4869                default:
4870                    super.handleMessage(msg);
4871                    break;
4872            }
4873        }
4874    }
4875
4876    // Class used to use a dropdown for a <select> element
4877    private class InvokeListBox implements Runnable {
4878        // Whether the listbox allows multiple selection.
4879        private boolean     mMultiple;
4880        // Passed in to a list with multiple selection to tell
4881        // which items are selected.
4882        private int[]       mSelectedArray;
4883        // Passed in to a list with single selection to tell
4884        // where the initial selection is.
4885        private int         mSelection;
4886
4887        private Container[] mContainers;
4888
4889        // Need these to provide stable ids to my ArrayAdapter,
4890        // which normally does not have stable ids. (Bug 1250098)
4891        private class Container extends Object {
4892            String  mString;
4893            boolean mEnabled;
4894            int     mId;
4895
4896            public String toString() {
4897                return mString;
4898            }
4899        }
4900
4901        /**
4902         *  Subclass ArrayAdapter so we can disable OptionGroupLabels,
4903         *  and allow filtering.
4904         */
4905        private class MyArrayListAdapter extends ArrayAdapter<Container> {
4906            public MyArrayListAdapter(Context context, Container[] objects, boolean multiple) {
4907                super(context,
4908                            multiple ? com.android.internal.R.layout.select_dialog_multichoice :
4909                            com.android.internal.R.layout.select_dialog_singlechoice,
4910                            objects);
4911            }
4912
4913            @Override
4914            public boolean hasStableIds() {
4915                // AdapterView's onChanged method uses this to determine whether
4916                // to restore the old state.  Return false so that the old (out
4917                // of date) state does not replace the new, valid state.
4918                return false;
4919            }
4920
4921            private Container item(int position) {
4922                if (position < 0 || position >= getCount()) {
4923                    return null;
4924                }
4925                return (Container) getItem(position);
4926            }
4927
4928            @Override
4929            public long getItemId(int position) {
4930                Container item = item(position);
4931                if (item == null) {
4932                    return -1;
4933                }
4934                return item.mId;
4935            }
4936
4937            @Override
4938            public boolean areAllItemsEnabled() {
4939                return false;
4940            }
4941
4942            @Override
4943            public boolean isEnabled(int position) {
4944                Container item = item(position);
4945                if (item == null) {
4946                    return false;
4947                }
4948                return item.mEnabled;
4949            }
4950        }
4951
4952        private InvokeListBox(String[] array,
4953                boolean[] enabled, int[] selected) {
4954            mMultiple = true;
4955            mSelectedArray = selected;
4956
4957            int length = array.length;
4958            mContainers = new Container[length];
4959            for (int i = 0; i < length; i++) {
4960                mContainers[i] = new Container();
4961                mContainers[i].mString = array[i];
4962                mContainers[i].mEnabled = enabled[i];
4963                mContainers[i].mId = i;
4964            }
4965        }
4966
4967        private InvokeListBox(String[] array, boolean[] enabled, int
4968                selection) {
4969            mSelection = selection;
4970            mMultiple = false;
4971
4972            int length = array.length;
4973            mContainers = new Container[length];
4974            for (int i = 0; i < length; i++) {
4975                mContainers[i] = new Container();
4976                mContainers[i].mString = array[i];
4977                mContainers[i].mEnabled = enabled[i];
4978                mContainers[i].mId = i;
4979            }
4980        }
4981
4982        /*
4983         * Whenever the data set changes due to filtering, this class ensures
4984         * that the checked item remains checked.
4985         */
4986        private class SingleDataSetObserver extends DataSetObserver {
4987            private long        mCheckedId;
4988            private ListView    mListView;
4989            private Adapter     mAdapter;
4990
4991            /*
4992             * Create a new observer.
4993             * @param id The ID of the item to keep checked.
4994             * @param l ListView for getting and clearing the checked states
4995             * @param a Adapter for getting the IDs
4996             */
4997            public SingleDataSetObserver(long id, ListView l, Adapter a) {
4998                mCheckedId = id;
4999                mListView = l;
5000                mAdapter = a;
5001            }
5002
5003            public void onChanged() {
5004                // The filter may have changed which item is checked.  Find the
5005                // item that the ListView thinks is checked.
5006                int position = mListView.getCheckedItemPosition();
5007                long id = mAdapter.getItemId(position);
5008                if (mCheckedId != id) {
5009                    // Clear the ListView's idea of the checked item, since
5010                    // it is incorrect
5011                    mListView.clearChoices();
5012                    // Search for mCheckedId.  If it is in the filtered list,
5013                    // mark it as checked
5014                    int count = mAdapter.getCount();
5015                    for (int i = 0; i < count; i++) {
5016                        if (mAdapter.getItemId(i) == mCheckedId) {
5017                            mListView.setItemChecked(i, true);
5018                            break;
5019                        }
5020                    }
5021                }
5022            }
5023
5024            public void onInvalidate() {}
5025        }
5026
5027        public void run() {
5028            final ListView listView = (ListView) LayoutInflater.from(mContext)
5029                    .inflate(com.android.internal.R.layout.select_dialog, null);
5030            final MyArrayListAdapter adapter = new
5031                    MyArrayListAdapter(mContext, mContainers, mMultiple);
5032            AlertDialog.Builder b = new AlertDialog.Builder(mContext)
5033                    .setView(listView).setCancelable(true)
5034                    .setInverseBackgroundForced(true);
5035
5036            if (mMultiple) {
5037                b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
5038                    public void onClick(DialogInterface dialog, int which) {
5039                        mWebViewCore.sendMessage(
5040                                EventHub.LISTBOX_CHOICES,
5041                                adapter.getCount(), 0,
5042                                listView.getCheckedItemPositions());
5043                    }});
5044                b.setNegativeButton(android.R.string.cancel,
5045                        new DialogInterface.OnClickListener() {
5046                    public void onClick(DialogInterface dialog, int which) {
5047                        mWebViewCore.sendMessage(
5048                                EventHub.SINGLE_LISTBOX_CHOICE, -2, 0);
5049                }});
5050            }
5051            final AlertDialog dialog = b.create();
5052            listView.setAdapter(adapter);
5053            listView.setFocusableInTouchMode(true);
5054            // There is a bug (1250103) where the checks in a ListView with
5055            // multiple items selected are associated with the positions, not
5056            // the ids, so the items do not properly retain their checks when
5057            // filtered.  Do not allow filtering on multiple lists until
5058            // that bug is fixed.
5059
5060            listView.setTextFilterEnabled(!mMultiple);
5061            if (mMultiple) {
5062                listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
5063                int length = mSelectedArray.length;
5064                for (int i = 0; i < length; i++) {
5065                    listView.setItemChecked(mSelectedArray[i], true);
5066                }
5067            } else {
5068                listView.setOnItemClickListener(new OnItemClickListener() {
5069                    public void onItemClick(AdapterView parent, View v,
5070                            int position, long id) {
5071                        mWebViewCore.sendMessage(
5072                                EventHub.SINGLE_LISTBOX_CHOICE, (int)id, 0);
5073                        dialog.dismiss();
5074                    }
5075                });
5076                if (mSelection != -1) {
5077                    listView.setSelection(mSelection);
5078                    listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
5079                    listView.setItemChecked(mSelection, true);
5080                    DataSetObserver observer = new SingleDataSetObserver(
5081                            adapter.getItemId(mSelection), listView, adapter);
5082                    adapter.registerDataSetObserver(observer);
5083                }
5084            }
5085            dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
5086                public void onCancel(DialogInterface dialog) {
5087                    mWebViewCore.sendMessage(
5088                                EventHub.SINGLE_LISTBOX_CHOICE, -2, 0);
5089                }
5090            });
5091            dialog.show();
5092        }
5093    }
5094
5095    /*
5096     * Request a dropdown menu for a listbox with multiple selection.
5097     *
5098     * @param array Labels for the listbox.
5099     * @param enabledArray  Which positions are enabled.
5100     * @param selectedArray Which positions are initally selected.
5101     */
5102    void requestListBox(String[] array, boolean[]enabledArray, int[]
5103            selectedArray) {
5104        mPrivateHandler.post(
5105                new InvokeListBox(array, enabledArray, selectedArray));
5106    }
5107
5108    /*
5109     * Request a dropdown menu for a listbox with single selection or a single
5110     * <select> element.
5111     *
5112     * @param array Labels for the listbox.
5113     * @param enabledArray  Which positions are enabled.
5114     * @param selection Which position is initally selected.
5115     */
5116    void requestListBox(String[] array, boolean[]enabledArray, int selection) {
5117        mPrivateHandler.post(
5118                new InvokeListBox(array, enabledArray, selection));
5119    }
5120
5121    // called by JNI
5122    private void sendMoveMouse(int frame, int node, int x, int y) {
5123        mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE,
5124                new WebViewCore.CursorData(frame, node, x, y));
5125    }
5126
5127    // called by JNI
5128    private void sendMoveMouseIfLatest() {
5129        mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE_IF_LATEST, cursorData());
5130    }
5131
5132    // called by JNI
5133    private void sendMotionUp(int touchGeneration,
5134            int frame, int node, int x, int y, int size) {
5135        WebViewCore.TouchUpData touchUpData = new WebViewCore.TouchUpData();
5136        touchUpData.mMoveGeneration = touchGeneration;
5137        touchUpData.mSize = size;
5138        touchUpData.mFrame = frame;
5139        touchUpData.mNode = node;
5140        touchUpData.mX = x;
5141        touchUpData.mY = y;
5142        mWebViewCore.sendMessage(EventHub.TOUCH_UP, touchUpData);
5143    }
5144
5145
5146    private int getScaledMaxXScroll() {
5147        int width;
5148        if (mHeightCanMeasure == false) {
5149            width = getViewWidth() / 4;
5150        } else {
5151            Rect visRect = new Rect();
5152            calcOurVisibleRect(visRect);
5153            width = visRect.width() / 2;
5154        }
5155        // FIXME the divisor should be retrieved from somewhere
5156        return viewToContent(width);
5157    }
5158
5159    private int getScaledMaxYScroll() {
5160        int height;
5161        if (mHeightCanMeasure == false) {
5162            height = getViewHeight() / 4;
5163        } else {
5164            Rect visRect = new Rect();
5165            calcOurVisibleRect(visRect);
5166            height = visRect.height() / 2;
5167        }
5168        // FIXME the divisor should be retrieved from somewhere
5169        // the closest thing today is hard-coded into ScrollView.java
5170        // (from ScrollView.java, line 363)   int maxJump = height/2;
5171        return viewToContent(height);
5172    }
5173
5174    /**
5175     * Called by JNI to invalidate view
5176     */
5177    private void viewInvalidate() {
5178        invalidate();
5179    }
5180
5181    // return true if the key was handled
5182    private boolean navHandledKey(int keyCode, int count, boolean noScroll
5183            , long time) {
5184        if (mNativeClass == 0) {
5185            return false;
5186        }
5187        mLastFocusTime = time;
5188        mLastFocusBounds = nativeGetCursorRingBounds();
5189        boolean keyHandled = nativeMoveFocus(keyCode, count, noScroll) == false;
5190        if (DebugFlags.WEB_VIEW) {
5191            Log.v(LOGTAG, "navHandledKey mLastFocusBounds=" + mLastFocusBounds
5192                    + " mLastFocusTime=" + mLastFocusTime
5193                    + " handled=" + keyHandled);
5194        }
5195        if (keyHandled == false || mHeightCanMeasure == false) {
5196            return keyHandled;
5197        }
5198        Rect contentFocus = nativeGetCursorRingBounds();
5199        if (contentFocus.isEmpty()) return keyHandled;
5200        Rect viewFocus = contentToView(contentFocus);
5201        Rect visRect = new Rect();
5202        calcOurVisibleRect(visRect);
5203        Rect outset = new Rect(visRect);
5204        int maxXScroll = visRect.width() / 2;
5205        int maxYScroll = visRect.height() / 2;
5206        outset.inset(-maxXScroll, -maxYScroll);
5207        if (Rect.intersects(outset, viewFocus) == false) {
5208            return keyHandled;
5209        }
5210        // FIXME: Necessary because ScrollView/ListView do not scroll left/right
5211        int maxH = Math.min(viewFocus.right - visRect.right, maxXScroll);
5212        if (maxH > 0) {
5213            pinScrollBy(maxH, 0, true, 0);
5214        } else {
5215            maxH = Math.max(viewFocus.left - visRect.left, -maxXScroll);
5216            if (maxH < 0) {
5217                pinScrollBy(maxH, 0, true, 0);
5218            }
5219        }
5220        if (mLastFocusBounds.isEmpty()) return keyHandled;
5221        if (mLastFocusBounds.equals(contentFocus)) return keyHandled;
5222        if (DebugFlags.WEB_VIEW) {
5223            Log.v(LOGTAG, "navHandledKey contentFocus=" + contentFocus);
5224        }
5225        requestRectangleOnScreen(viewFocus);
5226        mUserScroll = true;
5227        return keyHandled;
5228    }
5229
5230    /**
5231     * Set the background color. It's white by default. Pass
5232     * zero to make the view transparent.
5233     * @param color   the ARGB color described by Color.java
5234     */
5235    public void setBackgroundColor(int color) {
5236        mBackgroundColor = color;
5237        mWebViewCore.sendMessage(EventHub.SET_BACKGROUND_COLOR, color);
5238    }
5239
5240    public void debugDump() {
5241        nativeDebugDump();
5242        mWebViewCore.sendMessage(EventHub.DUMP_NAVTREE);
5243    }
5244
5245    /**
5246     *  Update our cache with updatedText.
5247     *  @param updatedText  The new text to put in our cache.
5248     */
5249    /* package */ void updateCachedTextfield(String updatedText) {
5250        // Also place our generation number so that when we look at the cache
5251        // we recognize that it is up to date.
5252        nativeUpdateCachedTextfield(updatedText, mTextGeneration);
5253    }
5254
5255    private native void     nativeClearFocus(int x, int y);
5256    private native void     nativeCreate(int ptr);
5257    private native int      nativeCursorFramePointer();
5258    private native Rect     nativeCursorNodeBounds();
5259    /* package */ native int nativeCursorNodePointer();
5260    /* package */ native boolean nativeCursorMatchesFocus();
5261    private native boolean  nativeCursorIntersects(Rect visibleRect);
5262    private native boolean  nativeCursorIsAnchor();
5263    private native boolean  nativeCursorIsTextInput();
5264    private native String   nativeCursorText();
5265    private native boolean  nativeCursorWantsKeyEvents();
5266    private native void     nativeDebugDump();
5267    private native void     nativeDestroy();
5268    private native void     nativeDrawCursorRing(Canvas content);
5269    private native void     nativeDrawMatches(Canvas canvas);
5270    private native void     nativeDrawSelection(Canvas content
5271            , int x, int y, boolean extendSelection);
5272    private native void     nativeDrawSelectionRegion(Canvas content);
5273    private native void     nativeDumpDisplayTree(String urlOrNull);
5274    private native int      nativeFindAll(String findLower, String findUpper);
5275    private native void     nativeFindNext(boolean forward);
5276    private native boolean  nativeFocusIsPassword();
5277    private native boolean  nativeFocusIsRtlText();
5278    private native boolean  nativeFocusIsTextField();
5279    private native boolean  nativeFocusIsTextInput();
5280    private native int      nativeFocusMaxLength();
5281    private native String   nativeFocusName();
5282    private native Rect     nativeFocusNodeBounds();
5283    /* package */ native int nativeFocusNodePointer();
5284    private native String   nativeFocusText();
5285    private native int      nativeFocusTextSize();
5286    /**
5287     * Returns true if the native focus nodes says it wants to handle key events
5288     * (ala plugins). This can only be called if mNativeClass is non-zero!
5289     */
5290    private native Rect     nativeGetCursorRingBounds();
5291    private native Region   nativeGetSelection();
5292    private native boolean  nativeHasCursorNode();
5293    private native boolean  nativeHasFocusNode();
5294    private native String   nativeImageURI(int x, int y);
5295    private native void     nativeInstrumentReport();
5296    private native void     nativeMarkNodeInvalid(int node);
5297    // return true if the page has been scrolled
5298    private native boolean  nativeMotionUp(int x, int y, int slop);
5299    // returns false if it handled the key
5300    private native boolean  nativeMoveFocus(int keyCode, int count,
5301            boolean noScroll);
5302    private native int      nativeMoveGeneration();
5303    private native void     nativeMoveSelection(int x, int y,
5304            boolean extendSelection);
5305    private native void     nativeRecomputeFocus();
5306    // Like many other of our native methods, you must make sure that
5307    // mNativeClass is not null before calling this method.
5308    private native void     nativeRecordButtons(boolean focused,
5309            boolean pressed, boolean invalidate);
5310    private native void     nativeSelectBestAt(Rect rect);
5311    private native void     nativeSetFindIsDown();
5312    private native void     nativeSetFollowedLink(boolean followed);
5313    private native void     nativeSetHeightCanMeasure(boolean measure);
5314    private native int      nativeTextGeneration();
5315    // Never call this version except by updateCachedTextfield(String) -
5316    // we always want to pass in our generation number.
5317    private native void     nativeUpdateCachedTextfield(String updatedText,
5318            int generation);
5319
5320}
5321