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