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