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