• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
ExtendedZoomControls(Context context, AttributeSet attrs)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 
show(boolean showZoom, boolean canZoomOut)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 
hide()227         public void hide() {
228             fade(View.GONE, 1.0f, 0.0f);
229         }
230 
fade(int visibility, float startAlpha, float endAlpha)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 
hasFocus()238         public boolean hasFocus() {
239             return mPlusMinusZoomControls.hasFocus();
240         }
241 
setOnZoomInClickListener(OnClickListener listener)242         public void setOnZoomInClickListener(OnClickListener listener) {
243             mPlusMinusZoomControls.setOnZoomInClickListener(listener);
244         }
245 
setOnZoomOutClickListener(OnClickListener listener)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          */
setWebView(WebView webview)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          */
getWebView()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          */
onNewPicture(WebView view, Picture picture)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 
HitTestResult()630         HitTestResult() {
631             mType = UNKNOWN_TYPE;
632         }
633 
setType(int type)634         private void setType(int type) {
635             mType = type;
636         }
637 
setExtra(String extra)638         private void setExtra(String extra) {
639             mExtra = extra;
640         }
641 
getType()642         public int getType() {
643             return mType;
644         }
645 
getExtra()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      */
WebView(Context context)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      */
WebView(Context context, AttributeSet attrs)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      */
WebView(Context context, AttributeSet attrs, int defStyle)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      */
WebView(Context context, AttributeSet attrs, int defStyle, Map<String, Object> javascriptInterfaces)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 
updateZoomButtonsEnabled()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 
init()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 
updateDefaultZoomDensity(int zoomDensity)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 
onSavePassword(String schemePlusHost, String username, String password, final Message resumeMsg)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
setScrollBarStyle(int style)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      */
setHorizontalScrollbarOverlay(boolean overlay)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      */
setVerticalScrollbarOverlay(boolean overlay)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      */
overlayHorizontalScrollbar()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      */
overlayVerticalScrollbar()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      */
getViewWidth()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      */
getTitleHeight()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      */
getVisibleTitleHeight()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      */
getViewHeight()942     /* package */ int getViewHeight() {
943         return getViewHeightWithTitle() - getVisibleTitleHeight();
944     }
945 
getViewHeightWithTitle()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      */
getCertificate()958     public SslCertificate getCertificate() {
959         return mCertificate;
960     }
961 
962     /**
963      * Sets the SSL certificate for the main top-level page.
964      */
setCertificate(SslCertificate certificate)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      */
savePassword(String host, String username, String password)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      */
setHttpAuthUsernamePassword(String host, String realm, String username, String password)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      */
getHttpAuthUsernamePassword(String host, String realm)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      */
destroy()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      */
enablePlatformNotifications()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      */
disablePlatformNotifications()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      */
setJsFlags(String flags)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      */
setNetworkAvailable(boolean networkUp)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      */
saveState(Bundle outState)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      */
savePicture(Bundle b, File dest)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      */
restorePicture(Bundle b, File src)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      */
restoreState(Bundle inState)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      */
loadUrl(String url)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      */
postUrl(String url, byte[] postData)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      */
loadData(String data, String mimeType, String encoding)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      */
loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String failUrl)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      */
stopLoading()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      */
reload()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      */
canGoBack()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      */
goBack()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      */
canGoForward()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      */
goForward()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      */
canGoBackOrForward(int steps)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      */
goBackOrForward(int steps)1462     public void goBackOrForward(int steps) {
1463         goBackOrForward(steps, false);
1464     }
1465 
goBackOrForward(int steps, boolean ignoreSnapshot)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 
extendScroll(int y)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      */
pageUp(boolean top)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      */
pageDown(boolean bottom)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      */
clearView()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      */
capturePicture()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      */
inEditingMode()1570     private boolean inEditingMode() {
1571         return mWebTextView != null && mWebTextView.getParent() != null
1572                 && mWebTextView.hasFocus();
1573     }
1574 
clearTextEntry()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      */
getScale()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      */
setInitialScale(int scaleInPercent)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      */
invokeZoomPicker()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      */
getHitTestResult()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?
requestFocusNodeHref(Message hrefMsg)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      */
requestImageRef(Message msg)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 
pinLoc(int x, int viewMax, int docMax)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
pinLocX(int x)1743     private int pinLocX(int x) {
1744         return pinLoc(x, getViewWidth(), computeHorizontalScrollRange());
1745     }
1746 
1747     // Expects y in view coordinates
pinLocY(int y)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      */
setEmbeddedTitleBar(View v)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      */
viewToContentDimension(int d)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      */
viewToContentX(int x)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      */
viewToContentY(int y)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      */
contentToViewDimension(int d)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      */
contentToViewX(int x)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      */
contentToViewY(int y)1848     /*package*/ int contentToViewY(int y) {
1849         return contentToViewDimension(y) + getTitleHeight();
1850     }
1851 
contentToViewRect(Rect x)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
viewInvalidate(int l, int t, int r, int b)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
viewInvalidateDelayed(long delay, int l, int t, int r, int b)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 
invalidateContentRect(Rect r)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
abortAnimation()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     */
recordNewContentSize(int w, int h, boolean updateLayout)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 
setNewZoomScale(float scale, boolean force)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 
sendOurVisibleRect()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
calcOurVisibleRect(Rect r)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
calcOurContentVisibleRect(Rect r)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      */
sendViewSizeZoom()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
computeHorizontalScrollRange()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
computeVerticalScrollRange()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
computeVerticalScrollOffset()2118     protected int computeVerticalScrollOffset() {
2119         return Math.max(mScrollY - getTitleHeight(), 0);
2120     }
2121 
2122     @Override
computeVerticalScrollExtent()2123     protected int computeVerticalScrollExtent() {
2124         return getViewHeight();
2125     }
2126 
2127     /** @hide */
2128     @Override
onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar, int l, int t, int r, int b)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      */
getUrl()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      */
getOriginalUrl()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      */
getTitle()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      */
getFavicon()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      */
getTouchIconUrl()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      */
getProgress()2193     public int getProgress() {
2194         return mCallbackProxy.getProgress();
2195     }
2196 
2197     /**
2198      * @return the height of the HTML content.
2199      */
getContentHeight()2200     public int getContentHeight() {
2201         return mContentHeight;
2202     }
2203 
2204     /**
2205      * @return the width of the HTML content.
2206      * @hide
2207      */
getContentWidth()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      */
pauseTimers()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      */
resumeTimers()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      */
onPause()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      */
onResume()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      */
isPaused()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      * @hide
2269      */
freeMemory()2270     public void freeMemory() {
2271         mWebViewCore.sendMessage(EventHub.FREE_MEMORY);
2272     }
2273 
2274     /**
2275      * Clear the resource cache. Note that the cache is per-application, so
2276      * this will clear the cache for all WebViews used.
2277      *
2278      * @param includeDiskFiles If false, only the RAM cache is cleared.
2279      */
clearCache(boolean includeDiskFiles)2280     public void clearCache(boolean includeDiskFiles) {
2281         // Note: this really needs to be a static method as it clears cache for all
2282         // WebView. But we need mWebViewCore to send message to WebCore thread, so
2283         // we can't make this static.
2284         mWebViewCore.sendMessage(EventHub.CLEAR_CACHE,
2285                 includeDiskFiles ? 1 : 0, 0);
2286     }
2287 
2288     /**
2289      * Make sure that clearing the form data removes the adapter from the
2290      * currently focused textfield if there is one.
2291      */
clearFormData()2292     public void clearFormData() {
2293         if (inEditingMode()) {
2294             AutoCompleteAdapter adapter = null;
2295             mWebTextView.setAdapterCustom(adapter);
2296         }
2297     }
2298 
2299     /**
2300      * Tell the WebView to clear its internal back/forward list.
2301      */
clearHistory()2302     public void clearHistory() {
2303         mCallbackProxy.getBackForwardList().setClearPending();
2304         mWebViewCore.sendMessage(EventHub.CLEAR_HISTORY);
2305     }
2306 
2307     /**
2308      * Clear the SSL preferences table stored in response to proceeding with SSL
2309      * certificate errors.
2310      */
clearSslPreferences()2311     public void clearSslPreferences() {
2312         mWebViewCore.sendMessage(EventHub.CLEAR_SSL_PREF_TABLE);
2313     }
2314 
2315     /**
2316      * Return the WebBackForwardList for this WebView. This contains the
2317      * back/forward list for use in querying each item in the history stack.
2318      * This is a copy of the private WebBackForwardList so it contains only a
2319      * snapshot of the current state. Multiple calls to this method may return
2320      * different objects. The object returned from this method will not be
2321      * updated to reflect any new state.
2322      */
copyBackForwardList()2323     public WebBackForwardList copyBackForwardList() {
2324         return mCallbackProxy.getBackForwardList().clone();
2325     }
2326 
2327     /*
2328      * Highlight and scroll to the next occurance of String in findAll.
2329      * Wraps the page infinitely, and scrolls.  Must be called after
2330      * calling findAll.
2331      *
2332      * @param forward Direction to search.
2333      */
findNext(boolean forward)2334     public void findNext(boolean forward) {
2335         if (0 == mNativeClass) return; // client isn't initialized
2336         nativeFindNext(forward);
2337     }
2338 
2339     /*
2340      * Find all instances of find on the page and highlight them.
2341      * @param find  String to find.
2342      * @return int  The number of occurances of the String "find"
2343      *              that were found.
2344      */
findAll(String find)2345     public int findAll(String find) {
2346         if (0 == mNativeClass) return 0; // client isn't initialized
2347         if (mFindIsUp == false) {
2348             recordNewContentSize(mContentWidth, mContentHeight + mFindHeight,
2349                     false);
2350             mFindIsUp = true;
2351         }
2352         int result = nativeFindAll(find.toLowerCase(), find.toUpperCase());
2353         invalidate();
2354         return result;
2355     }
2356 
2357     // Used to know whether the find dialog is open.  Affects whether
2358     // or not we draw the highlights for matches.
2359     private boolean mFindIsUp;
2360     private int mFindHeight;
2361 
2362     /**
2363      * Return the first substring consisting of the address of a physical
2364      * location. Currently, only addresses in the United States are detected,
2365      * and consist of:
2366      * - a house number
2367      * - a street name
2368      * - a street type (Road, Circle, etc), either spelled out or abbreviated
2369      * - a city name
2370      * - a state or territory, either spelled out or two-letter abbr.
2371      * - an optional 5 digit or 9 digit zip code.
2372      *
2373      * All names must be correctly capitalized, and the zip code, if present,
2374      * must be valid for the state. The street type must be a standard USPS
2375      * spelling or abbreviation. The state or territory must also be spelled
2376      * or abbreviated using USPS standards. The house number may not exceed
2377      * five digits.
2378      * @param addr The string to search for addresses.
2379      *
2380      * @return the address, or if no address is found, return null.
2381      */
findAddress(String addr)2382     public static String findAddress(String addr) {
2383         return findAddress(addr, false);
2384     }
2385 
2386     /**
2387      * @hide
2388      * Return the first substring consisting of the address of a physical
2389      * location. Currently, only addresses in the United States are detected,
2390      * and consist of:
2391      * - a house number
2392      * - a street name
2393      * - a street type (Road, Circle, etc), either spelled out or abbreviated
2394      * - a city name
2395      * - a state or territory, either spelled out or two-letter abbr.
2396      * - an optional 5 digit or 9 digit zip code.
2397      *
2398      * Names are optionally capitalized, and the zip code, if present,
2399      * must be valid for the state. The street type must be a standard USPS
2400      * spelling or abbreviation. The state or territory must also be spelled
2401      * or abbreviated using USPS standards. The house number may not exceed
2402      * five digits.
2403      * @param addr The string to search for addresses.
2404      * @param caseInsensitive addr Set to true to make search ignore case.
2405      *
2406      * @return the address, or if no address is found, return null.
2407      */
findAddress(String addr, boolean caseInsensitive)2408     public static String findAddress(String addr, boolean caseInsensitive) {
2409         return WebViewCore.nativeFindAddress(addr, caseInsensitive);
2410     }
2411 
2412     /*
2413      * Clear the highlighting surrounding text matches created by findAll.
2414      */
clearMatches()2415     public void clearMatches() {
2416         if (mFindIsUp) {
2417             recordNewContentSize(mContentWidth, mContentHeight - mFindHeight,
2418                     false);
2419             mFindIsUp = false;
2420         }
2421         nativeSetFindIsDown();
2422         // Now that the dialog has been removed, ensure that we scroll to a
2423         // location that is not beyond the end of the page.
2424         pinScrollTo(mScrollX, mScrollY, false, 0);
2425         invalidate();
2426     }
2427 
2428     /**
2429      * @hide
2430      */
setFindDialogHeight(int height)2431     public void setFindDialogHeight(int height) {
2432         if (DebugFlags.WEB_VIEW) {
2433             Log.v(LOGTAG, "setFindDialogHeight height=" + height);
2434         }
2435         mFindHeight = height;
2436     }
2437 
2438     /**
2439      * Query the document to see if it contains any image references. The
2440      * message object will be dispatched with arg1 being set to 1 if images
2441      * were found and 0 if the document does not reference any images.
2442      * @param response The message that will be dispatched with the result.
2443      */
documentHasImages(Message response)2444     public void documentHasImages(Message response) {
2445         if (response == null) {
2446             return;
2447         }
2448         mWebViewCore.sendMessage(EventHub.DOC_HAS_IMAGES, response);
2449     }
2450 
2451     @Override
computeScroll()2452     public void computeScroll() {
2453         if (mScroller.computeScrollOffset()) {
2454             int oldX = mScrollX;
2455             int oldY = mScrollY;
2456             mScrollX = mScroller.getCurrX();
2457             mScrollY = mScroller.getCurrY();
2458             postInvalidate();  // So we draw again
2459             if (oldX != mScrollX || oldY != mScrollY) {
2460                 // as onScrollChanged() is not called, sendOurVisibleRect()
2461                 // needs to be call explicitly
2462                 sendOurVisibleRect();
2463             }
2464         } else {
2465             super.computeScroll();
2466         }
2467     }
2468 
computeDuration(int dx, int dy)2469     private static int computeDuration(int dx, int dy) {
2470         int distance = Math.max(Math.abs(dx), Math.abs(dy));
2471         int duration = distance * 1000 / STD_SPEED;
2472         return Math.min(duration, MAX_DURATION);
2473     }
2474 
2475     // helper to pin the scrollBy parameters (already in view coordinates)
2476     // returns true if the scroll was changed
pinScrollBy(int dx, int dy, boolean animate, int animationDuration)2477     private boolean pinScrollBy(int dx, int dy, boolean animate, int animationDuration) {
2478         return pinScrollTo(mScrollX + dx, mScrollY + dy, animate, animationDuration);
2479     }
2480     // helper to pin the scrollTo parameters (already in view coordinates)
2481     // returns true if the scroll was changed
pinScrollTo(int x, int y, boolean animate, int animationDuration)2482     private boolean pinScrollTo(int x, int y, boolean animate, int animationDuration) {
2483         x = pinLocX(x);
2484         y = pinLocY(y);
2485         int dx = x - mScrollX;
2486         int dy = y - mScrollY;
2487 
2488         if ((dx | dy) == 0) {
2489             return false;
2490         }
2491         if (animate) {
2492             //        Log.d(LOGTAG, "startScroll: " + dx + " " + dy);
2493             mScroller.startScroll(mScrollX, mScrollY, dx, dy,
2494                     animationDuration > 0 ? animationDuration : computeDuration(dx, dy));
2495             awakenScrollBars(mScroller.getDuration());
2496             invalidate();
2497         } else {
2498             abortAnimation(); // just in case
2499             scrollTo(x, y);
2500         }
2501         return true;
2502     }
2503 
2504     // Scale from content to view coordinates, and pin.
2505     // Also called by jni webview.cpp
setContentScrollBy(int cx, int cy, boolean animate)2506     private boolean setContentScrollBy(int cx, int cy, boolean animate) {
2507         if (mDrawHistory) {
2508             // disallow WebView to change the scroll position as History Picture
2509             // is used in the view system.
2510             // TODO: as we switchOutDrawHistory when trackball or navigation
2511             // keys are hit, this should be safe. Right?
2512             return false;
2513         }
2514         cx = contentToViewDimension(cx);
2515         cy = contentToViewDimension(cy);
2516         if (mHeightCanMeasure) {
2517             // move our visible rect according to scroll request
2518             if (cy != 0) {
2519                 Rect tempRect = new Rect();
2520                 calcOurVisibleRect(tempRect);
2521                 tempRect.offset(cx, cy);
2522                 requestRectangleOnScreen(tempRect);
2523             }
2524             // FIXME: We scroll horizontally no matter what because currently
2525             // ScrollView and ListView will not scroll horizontally.
2526             // FIXME: Why do we only scroll horizontally if there is no
2527             // vertical scroll?
2528 //                Log.d(LOGTAG, "setContentScrollBy cy=" + cy);
2529             return cy == 0 && cx != 0 && pinScrollBy(cx, 0, animate, 0);
2530         } else {
2531             return pinScrollBy(cx, cy, animate, 0);
2532         }
2533     }
2534 
2535     // scale from content to view coordinates, and pin
2536     // return true if pin caused the final x/y different than the request cx/cy,
2537     // and a future scroll may reach the request cx/cy after our size has
2538     // changed
2539     // return false if the view scroll to the exact position as it is requested,
2540     // where negative numbers are taken to mean 0
setContentScrollTo(int cx, int cy)2541     private boolean setContentScrollTo(int cx, int cy) {
2542         if (mDrawHistory) {
2543             // disallow WebView to change the scroll position as History Picture
2544             // is used in the view system.
2545             // One known case where this is called is that WebCore tries to
2546             // restore the scroll position. As history Picture already uses the
2547             // saved scroll position, it is ok to skip this.
2548             return false;
2549         }
2550         int vx;
2551         int vy;
2552         if ((cx | cy) == 0) {
2553             // If the page is being scrolled to (0,0), do not add in the title
2554             // bar's height, and simply scroll to (0,0). (The only other work
2555             // in contentToView_ is to multiply, so this would not change 0.)
2556             vx = 0;
2557             vy = 0;
2558         } else {
2559             vx = contentToViewX(cx);
2560             vy = contentToViewY(cy);
2561         }
2562 //        Log.d(LOGTAG, "content scrollTo [" + cx + " " + cy + "] view=[" +
2563 //                      vx + " " + vy + "]");
2564         // Some mobile sites attempt to scroll the title bar off the page by
2565         // scrolling to (0,1).  If we are at the top left corner of the
2566         // page, assume this is an attempt to scroll off the title bar, and
2567         // animate the title bar off screen slowly enough that the user can see
2568         // it.
2569         if (cx == 0 && cy == 1 && mScrollX == 0 && mScrollY == 0) {
2570             pinScrollTo(vx, vy, true, SLIDE_TITLE_DURATION);
2571             // Since we are animating, we have not yet reached the desired
2572             // scroll position.  Do not return true to request another attempt
2573             return false;
2574         }
2575         pinScrollTo(vx, vy, false, 0);
2576         // If the request was to scroll to a negative coordinate, treat it as if
2577         // it was a request to scroll to 0
2578         if ((mScrollX != vx && cx >= 0) || (mScrollY != vy && cy >= 0)) {
2579             return true;
2580         } else {
2581             return false;
2582         }
2583     }
2584 
2585     // scale from content to view coordinates, and pin
spawnContentScrollTo(int cx, int cy)2586     private void spawnContentScrollTo(int cx, int cy) {
2587         if (mDrawHistory) {
2588             // disallow WebView to change the scroll position as History Picture
2589             // is used in the view system.
2590             return;
2591         }
2592         int vx = contentToViewX(cx);
2593         int vy = contentToViewY(cy);
2594         pinScrollTo(vx, vy, true, 0);
2595     }
2596 
2597     /**
2598      * These are from webkit, and are in content coordinate system (unzoomed)
2599      */
contentSizeChanged(boolean updateLayout)2600     private void contentSizeChanged(boolean updateLayout) {
2601         // suppress 0,0 since we usually see real dimensions soon after
2602         // this avoids drawing the prev content in a funny place. If we find a
2603         // way to consolidate these notifications, this check may become
2604         // obsolete
2605         if ((mContentWidth | mContentHeight) == 0) {
2606             return;
2607         }
2608 
2609         if (mHeightCanMeasure) {
2610             if (getMeasuredHeight() != contentToViewDimension(mContentHeight)
2611                     && updateLayout) {
2612                 requestLayout();
2613             }
2614         } else if (mWidthCanMeasure) {
2615             if (getMeasuredWidth() != contentToViewDimension(mContentWidth)
2616                     && updateLayout) {
2617                 requestLayout();
2618             }
2619         } else {
2620             // If we don't request a layout, try to send our view size to the
2621             // native side to ensure that WebCore has the correct dimensions.
2622             sendViewSizeZoom();
2623         }
2624     }
2625 
2626     /**
2627      * Set the WebViewClient that will receive various notifications and
2628      * requests. This will replace the current handler.
2629      * @param client An implementation of WebViewClient.
2630      */
setWebViewClient(WebViewClient client)2631     public void setWebViewClient(WebViewClient client) {
2632         mCallbackProxy.setWebViewClient(client);
2633     }
2634 
2635     /**
2636      * Register the interface to be used when content can not be handled by
2637      * the rendering engine, and should be downloaded instead. This will replace
2638      * the current handler.
2639      * @param listener An implementation of DownloadListener.
2640      */
setDownloadListener(DownloadListener listener)2641     public void setDownloadListener(DownloadListener listener) {
2642         mCallbackProxy.setDownloadListener(listener);
2643     }
2644 
2645     /**
2646      * Set the chrome handler. This is an implementation of WebChromeClient for
2647      * use in handling Javascript dialogs, favicons, titles, and the progress.
2648      * This will replace the current handler.
2649      * @param client An implementation of WebChromeClient.
2650      */
setWebChromeClient(WebChromeClient client)2651     public void setWebChromeClient(WebChromeClient client) {
2652         mCallbackProxy.setWebChromeClient(client);
2653     }
2654 
2655     /**
2656      * Gets the chrome handler.
2657      * @return the current WebChromeClient instance.
2658      *
2659      * @hide API council approval.
2660      */
getWebChromeClient()2661     public WebChromeClient getWebChromeClient() {
2662         return mCallbackProxy.getWebChromeClient();
2663     }
2664 
2665     /**
2666      * Set the Picture listener. This is an interface used to receive
2667      * notifications of a new Picture.
2668      * @param listener An implementation of WebView.PictureListener.
2669      */
setPictureListener(PictureListener listener)2670     public void setPictureListener(PictureListener listener) {
2671         mPictureListener = listener;
2672     }
2673 
2674     /**
2675      * {@hide}
2676      */
2677     /* FIXME: Debug only! Remove for SDK! */
externalRepresentation(Message callback)2678     public void externalRepresentation(Message callback) {
2679         mWebViewCore.sendMessage(EventHub.REQUEST_EXT_REPRESENTATION, callback);
2680     }
2681 
2682     /**
2683      * {@hide}
2684      */
2685     /* FIXME: Debug only! Remove for SDK! */
documentAsText(Message callback)2686     public void documentAsText(Message callback) {
2687         mWebViewCore.sendMessage(EventHub.REQUEST_DOC_AS_TEXT, callback);
2688     }
2689 
2690     /**
2691      * Use this function to bind an object to Javascript so that the
2692      * methods can be accessed from Javascript.
2693      * <p><strong>IMPORTANT:</strong>
2694      * <ul>
2695      * <li> Using addJavascriptInterface() allows JavaScript to control your
2696      * application. This can be a very useful feature or a dangerous security
2697      * issue. When the HTML in the WebView is untrustworthy (for example, part
2698      * or all of the HTML is provided by some person or process), then an
2699      * attacker could inject HTML that will execute your code and possibly any
2700      * code of the attacker's choosing.<br>
2701      * Do not use addJavascriptInterface() unless all of the HTML in this
2702      * WebView was written by you.</li>
2703      * <li> The Java object that is bound runs in another thread and not in
2704      * the thread that it was constructed in.</li>
2705      * </ul></p>
2706      * @param obj The class instance to bind to Javascript
2707      * @param interfaceName The name to used to expose the class in Javascript
2708      */
addJavascriptInterface(Object obj, String interfaceName)2709     public void addJavascriptInterface(Object obj, String interfaceName) {
2710         WebViewCore.JSInterfaceData arg = new WebViewCore.JSInterfaceData();
2711         arg.mObject = obj;
2712         arg.mInterfaceName = interfaceName;
2713         mWebViewCore.sendMessage(EventHub.ADD_JS_INTERFACE, arg);
2714     }
2715 
2716     /**
2717      * Return the WebSettings object used to control the settings for this
2718      * WebView.
2719      * @return A WebSettings object that can be used to control this WebView's
2720      *         settings.
2721      */
getSettings()2722     public WebSettings getSettings() {
2723         return mWebViewCore.getSettings();
2724     }
2725 
2726    /**
2727     * Return the list of currently loaded plugins.
2728     * @return The list of currently loaded plugins.
2729     *
2730     * @deprecated This was used for Gears, which has been deprecated.
2731     */
2732     @Deprecated
getPluginList()2733     public static synchronized PluginList getPluginList() {
2734         return new PluginList();
2735     }
2736 
2737    /**
2738     * @deprecated This was used for Gears, which has been deprecated.
2739     */
2740     @Deprecated
refreshPlugins(boolean reloadOpenPages)2741     public void refreshPlugins(boolean reloadOpenPages) { }
2742 
2743     //-------------------------------------------------------------------------
2744     // Override View methods
2745     //-------------------------------------------------------------------------
2746 
2747     @Override
finalize()2748     protected void finalize() throws Throwable {
2749         try {
2750             destroy();
2751         } finally {
2752             super.finalize();
2753         }
2754     }
2755 
2756     @Override
drawChild(Canvas canvas, View child, long drawingTime)2757     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
2758         if (child == mTitleBar) {
2759             // When drawing the title bar, move it horizontally to always show
2760             // at the top of the WebView.
2761             mTitleBar.offsetLeftAndRight(mScrollX - mTitleBar.getLeft());
2762         }
2763         return super.drawChild(canvas, child, drawingTime);
2764     }
2765 
2766     @Override
onDraw(Canvas canvas)2767     protected void onDraw(Canvas canvas) {
2768         // if mNativeClass is 0, the WebView has been destroyed. Do nothing.
2769         if (mNativeClass == 0) {
2770             return;
2771         }
2772         int saveCount = canvas.save();
2773         if (mTitleBar != null) {
2774             canvas.translate(0, (int) mTitleBar.getHeight());
2775         }
2776         // Update the buttons in the picture, so when we draw the picture
2777         // to the screen, they are in the correct state.
2778         // Tell the native side if user is a) touching the screen,
2779         // b) pressing the trackball down, or c) pressing the enter key
2780         // If the cursor is on a button, we need to draw it in the pressed
2781         // state.
2782         // If mNativeClass is 0, we should not reach here, so we do not
2783         // need to check it again.
2784         nativeRecordButtons(hasFocus() && hasWindowFocus(),
2785                 mTouchMode == TOUCH_SHORTPRESS_START_MODE
2786                 || mTrackballDown || mGotCenterDown, false);
2787         drawCoreAndCursorRing(canvas, mBackgroundColor, mDrawCursorRing);
2788         canvas.restoreToCount(saveCount);
2789 
2790         // Now draw the shadow.
2791         if (mTitleBar != null) {
2792             int y = mScrollY + getVisibleTitleHeight();
2793             int height = (int) (5f * getContext().getResources()
2794                     .getDisplayMetrics().density);
2795             mTitleShadow.setBounds(mScrollX, y, mScrollX + getWidth(),
2796                     y + height);
2797             mTitleShadow.draw(canvas);
2798         }
2799         if (AUTO_REDRAW_HACK && mAutoRedraw) {
2800             invalidate();
2801         }
2802     }
2803 
2804     @Override
setLayoutParams(ViewGroup.LayoutParams params)2805     public void setLayoutParams(ViewGroup.LayoutParams params) {
2806         if (params.height == LayoutParams.WRAP_CONTENT) {
2807             mWrapContent = true;
2808         }
2809         super.setLayoutParams(params);
2810     }
2811 
2812     @Override
performLongClick()2813     public boolean performLongClick() {
2814         if (mNativeClass != 0 && nativeCursorIsTextInput()) {
2815             // Send the click so that the textfield is in focus
2816             // FIXME: When we start respecting changes to the native textfield's
2817             // selection, need to make sure that this does not change it.
2818             mWebViewCore.sendMessage(EventHub.CLICK, nativeCursorFramePointer(),
2819                     nativeCursorNodePointer());
2820             rebuildWebTextView();
2821         }
2822         if (inEditingMode()) {
2823             return mWebTextView.performLongClick();
2824         } else {
2825             return super.performLongClick();
2826         }
2827     }
2828 
inAnimateZoom()2829     boolean inAnimateZoom() {
2830         return mZoomScale != 0;
2831     }
2832 
2833     /**
2834      * Need to adjust the WebTextView after a change in zoom, since mActualScale
2835      * has changed.  This is especially important for password fields, which are
2836      * drawn by the WebTextView, since it conveys more information than what
2837      * webkit draws.  Thus we need to reposition it to show in the correct
2838      * place.
2839      */
2840     private boolean mNeedToAdjustWebTextView;
2841 
drawCoreAndCursorRing(Canvas canvas, int color, boolean drawCursorRing)2842     private void drawCoreAndCursorRing(Canvas canvas, int color,
2843         boolean drawCursorRing) {
2844         if (mDrawHistory) {
2845             canvas.scale(mActualScale, mActualScale);
2846             canvas.drawPicture(mHistoryPicture);
2847             return;
2848         }
2849 
2850         boolean animateZoom = mZoomScale != 0;
2851         boolean animateScroll = !mScroller.isFinished()
2852                 || mVelocityTracker != null;
2853         if (animateZoom) {
2854             float zoomScale;
2855             int interval = (int) (SystemClock.uptimeMillis() - mZoomStart);
2856             if (interval < ZOOM_ANIMATION_LENGTH) {
2857                 float ratio = (float) interval / ZOOM_ANIMATION_LENGTH;
2858                 zoomScale = 1.0f / (mInvInitialZoomScale
2859                         + (mInvFinalZoomScale - mInvInitialZoomScale) * ratio);
2860                 invalidate();
2861             } else {
2862                 zoomScale = mZoomScale;
2863                 // set mZoomScale to be 0 as we have done animation
2864                 mZoomScale = 0;
2865                 // call invalidate() again to draw with the final filters
2866                 invalidate();
2867                 if (mNeedToAdjustWebTextView) {
2868                     mNeedToAdjustWebTextView = false;
2869                     Rect contentBounds = nativeFocusCandidateNodeBounds();
2870                     Rect vBox = contentToViewRect(contentBounds);
2871                     Rect visibleRect = new Rect();
2872                     calcOurVisibleRect(visibleRect);
2873                     if (visibleRect.contains(vBox)) {
2874                         // As a result of the zoom, the textfield is now on
2875                         // screen.  Place the WebTextView in its new place,
2876                         // accounting for our new scroll/zoom values.
2877                         mWebTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
2878                                 contentToViewDimension(
2879                                 nativeFocusCandidateTextSize()));
2880                         mWebTextView.setRect(vBox.left, vBox.top, vBox.width(),
2881                                 vBox.height());
2882                         // If it is a password field, start drawing the
2883                         // WebTextView once again.
2884                         if (nativeFocusCandidateIsPassword()) {
2885                             mWebTextView.setInPassword(true);
2886                         }
2887                     } else {
2888                         // The textfield is now off screen.  The user probably
2889                         // was not zooming to see the textfield better.  Remove
2890                         // the WebTextView.  If the user types a key, and the
2891                         // textfield is still in focus, we will reconstruct
2892                         // the WebTextView and scroll it back on screen.
2893                         mWebTextView.remove();
2894                     }
2895                 }
2896             }
2897             // calculate the intermediate scroll position. As we need to use
2898             // zoomScale, we can't use pinLocX/Y directly. Copy the logic here.
2899             float scale = zoomScale * mInvInitialZoomScale;
2900             int tx = Math.round(scale * (mInitialScrollX + mZoomCenterX)
2901                     - mZoomCenterX);
2902             tx = -pinLoc(tx, getViewWidth(), Math.round(mContentWidth
2903                     * zoomScale)) + mScrollX;
2904             int titleHeight = getTitleHeight();
2905             int ty = Math.round(scale
2906                     * (mInitialScrollY + mZoomCenterY - titleHeight)
2907                     - (mZoomCenterY - titleHeight));
2908             ty = -(ty <= titleHeight ? Math.max(ty, 0) : pinLoc(ty
2909                     - titleHeight, getViewHeight(), Math.round(mContentHeight
2910                     * zoomScale)) + titleHeight) + mScrollY;
2911             canvas.translate(tx, ty);
2912             canvas.scale(zoomScale, zoomScale);
2913             if (inEditingMode() && !mNeedToAdjustWebTextView
2914                     && mZoomScale != 0) {
2915                 // The WebTextView is up.  Keep track of this so we can adjust
2916                 // its size and placement when we finish zooming
2917                 mNeedToAdjustWebTextView = true;
2918                 // If it is in password mode, turn it off so it does not draw
2919                 // misplaced.
2920                 if (nativeFocusCandidateIsPassword()) {
2921                     mWebTextView.setInPassword(false);
2922                 }
2923             }
2924         } else {
2925             canvas.scale(mActualScale, mActualScale);
2926         }
2927 
2928         mWebViewCore.drawContentPicture(canvas, color, animateZoom,
2929                 animateScroll);
2930 
2931         if (mNativeClass == 0) return;
2932         if (mShiftIsPressed && !animateZoom) {
2933             if (mTouchSelection) {
2934                 nativeDrawSelectionRegion(canvas);
2935             } else {
2936                 nativeDrawSelection(canvas, mInvActualScale, getTitleHeight(),
2937                         mSelectX, mSelectY, mExtendSelection);
2938             }
2939         } else if (drawCursorRing) {
2940             if (mTouchMode == TOUCH_SHORTPRESS_START_MODE) {
2941                 mTouchMode = TOUCH_SHORTPRESS_MODE;
2942                 HitTestResult hitTest = getHitTestResult();
2943                 if (hitTest != null &&
2944                         hitTest.mType != HitTestResult.UNKNOWN_TYPE) {
2945                     mPrivateHandler.sendMessageDelayed(mPrivateHandler
2946                             .obtainMessage(SWITCH_TO_LONGPRESS),
2947                             LONG_PRESS_TIMEOUT);
2948                 }
2949             }
2950             nativeDrawCursorRing(canvas);
2951         }
2952         // When the FindDialog is up, only draw the matches if we are not in
2953         // the process of scrolling them into view.
2954         if (mFindIsUp && !animateScroll) {
2955             nativeDrawMatches(canvas);
2956         }
2957     }
2958 
2959     // draw history
2960     private boolean mDrawHistory = false;
2961     private Picture mHistoryPicture = null;
2962     private int mHistoryWidth = 0;
2963     private int mHistoryHeight = 0;
2964 
2965     // Only check the flag, can be called from WebCore thread
drawHistory()2966     boolean drawHistory() {
2967         return mDrawHistory;
2968     }
2969 
2970     // Should only be called in UI thread
switchOutDrawHistory()2971     void switchOutDrawHistory() {
2972         if (null == mWebViewCore) return; // CallbackProxy may trigger this
2973         if (mDrawHistory && mWebViewCore.pictureReady()) {
2974             mDrawHistory = false;
2975             invalidate();
2976             int oldScrollX = mScrollX;
2977             int oldScrollY = mScrollY;
2978             mScrollX = pinLocX(mScrollX);
2979             mScrollY = pinLocY(mScrollY);
2980             if (oldScrollX != mScrollX || oldScrollY != mScrollY) {
2981                 mUserScroll = false;
2982                 mWebViewCore.sendMessage(EventHub.SYNC_SCROLL, oldScrollX,
2983                         oldScrollY);
2984             }
2985             sendOurVisibleRect();
2986         }
2987     }
2988 
cursorData()2989     WebViewCore.CursorData cursorData() {
2990         WebViewCore.CursorData result = new WebViewCore.CursorData();
2991         result.mMoveGeneration = nativeMoveGeneration();
2992         result.mFrame = nativeCursorFramePointer();
2993         Point position = nativeCursorPosition();
2994         result.mX = position.x;
2995         result.mY = position.y;
2996         return result;
2997     }
2998 
2999     /**
3000      *  Delete text from start to end in the focused textfield. If there is no
3001      *  focus, or if start == end, silently fail.  If start and end are out of
3002      *  order, swap them.
3003      *  @param  start   Beginning of selection to delete.
3004      *  @param  end     End of selection to delete.
3005      */
deleteSelection(int start, int end)3006     /* package */ void deleteSelection(int start, int end) {
3007         mTextGeneration++;
3008         WebViewCore.TextSelectionData data
3009                 = new WebViewCore.TextSelectionData(start, end);
3010         mWebViewCore.sendMessage(EventHub.DELETE_SELECTION, mTextGeneration, 0,
3011                 data);
3012     }
3013 
3014     /**
3015      *  Set the selection to (start, end) in the focused textfield. If start and
3016      *  end are out of order, swap them.
3017      *  @param  start   Beginning of selection.
3018      *  @param  end     End of selection.
3019      */
setSelection(int start, int end)3020     /* package */ void setSelection(int start, int end) {
3021         mWebViewCore.sendMessage(EventHub.SET_SELECTION, start, end);
3022     }
3023 
3024     // Called by JNI when a touch event puts a textfield into focus.
displaySoftKeyboard(boolean isTextView)3025     private void displaySoftKeyboard(boolean isTextView) {
3026         InputMethodManager imm = (InputMethodManager)
3027                 getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
3028 
3029         if (isTextView) {
3030             if (mWebTextView == null) return;
3031 
3032             imm.showSoftInput(mWebTextView, 0);
3033             if (mInZoomOverview) {
3034                 // if in zoom overview mode, call doDoubleTap() to bring it back
3035                 // to normal mode so that user can enter text.
3036                 doDoubleTap();
3037             }
3038         }
3039         else { // used by plugins
3040             imm.showSoftInput(this, 0);
3041         }
3042     }
3043 
3044     // Called by WebKit to instruct the UI to hide the keyboard
hideSoftKeyboard()3045     private void hideSoftKeyboard() {
3046         InputMethodManager imm = (InputMethodManager)
3047                 getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
3048 
3049         imm.hideSoftInputFromWindow(this.getWindowToken(), 0);
3050     }
3051 
3052     /*
3053      * This method checks the current focus and cursor and potentially rebuilds
3054      * mWebTextView to have the appropriate properties, such as password,
3055      * multiline, and what text it contains.  It also removes it if necessary.
3056      */
rebuildWebTextView()3057     /* package */ void rebuildWebTextView() {
3058         // If the WebView does not have focus, do nothing until it gains focus.
3059         if (!hasFocus() && (null == mWebTextView || !mWebTextView.hasFocus())) {
3060             return;
3061         }
3062         boolean alreadyThere = inEditingMode();
3063         // inEditingMode can only return true if mWebTextView is non-null,
3064         // so we can safely call remove() if (alreadyThere)
3065         if (0 == mNativeClass || !nativeFocusCandidateIsTextInput()) {
3066             if (alreadyThere) {
3067                 mWebTextView.remove();
3068             }
3069             return;
3070         }
3071         // At this point, we know we have found an input field, so go ahead
3072         // and create the WebTextView if necessary.
3073         if (mWebTextView == null) {
3074             mWebTextView = new WebTextView(mContext, WebView.this);
3075             // Initialize our generation number.
3076             mTextGeneration = 0;
3077         }
3078         mWebTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
3079                 contentToViewDimension(nativeFocusCandidateTextSize()));
3080         Rect visibleRect = new Rect();
3081         calcOurContentVisibleRect(visibleRect);
3082         // Note that sendOurVisibleRect calls viewToContent, so the coordinates
3083         // should be in content coordinates.
3084         Rect bounds = nativeFocusCandidateNodeBounds();
3085         if (!Rect.intersects(bounds, visibleRect)) {
3086             mWebTextView.bringIntoView();
3087         }
3088         String text = nativeFocusCandidateText();
3089         int nodePointer = nativeFocusCandidatePointer();
3090         if (alreadyThere && mWebTextView.isSameTextField(nodePointer)) {
3091             // It is possible that we have the same textfield, but it has moved,
3092             // i.e. In the case of opening/closing the screen.
3093             // In that case, we need to set the dimensions, but not the other
3094             // aspects.
3095             // We also need to restore the selection, which gets wrecked by
3096             // calling setTextEntryRect.
3097             Spannable spannable = (Spannable) mWebTextView.getText();
3098             int start = Selection.getSelectionStart(spannable);
3099             int end = Selection.getSelectionEnd(spannable);
3100             // If the text has been changed by webkit, update it.  However, if
3101             // there has been more UI text input, ignore it.  We will receive
3102             // another update when that text is recognized.
3103             if (text != null && !text.equals(spannable.toString())
3104                     && nativeTextGeneration() == mTextGeneration) {
3105                 mWebTextView.setTextAndKeepSelection(text);
3106             } else {
3107                 Selection.setSelection(spannable, start, end);
3108             }
3109         } else {
3110             Rect vBox = contentToViewRect(bounds);
3111             mWebTextView.setRect(vBox.left, vBox.top, vBox.width(),
3112                     vBox.height());
3113             mWebTextView.setGravity(nativeFocusCandidateIsRtlText() ?
3114                     Gravity.RIGHT : Gravity.NO_GRAVITY);
3115             // this needs to be called before update adapter thread starts to
3116             // ensure the mWebTextView has the same node pointer
3117             mWebTextView.setNodePointer(nodePointer);
3118             int maxLength = -1;
3119             boolean isTextField = nativeFocusCandidateIsTextField();
3120             if (isTextField) {
3121                 maxLength = nativeFocusCandidateMaxLength();
3122                 String name = nativeFocusCandidateName();
3123                 if (mWebViewCore.getSettings().getSaveFormData()
3124                         && name != null) {
3125                     Message update = mPrivateHandler.obtainMessage(
3126                             REQUEST_FORM_DATA, nodePointer);
3127                     RequestFormData updater = new RequestFormData(name,
3128                             getUrl(), update);
3129                     Thread t = new Thread(updater);
3130                     t.start();
3131                 }
3132             }
3133             mWebTextView.setMaxLength(maxLength);
3134             AutoCompleteAdapter adapter = null;
3135             mWebTextView.setAdapterCustom(adapter);
3136             mWebTextView.setSingleLine(isTextField);
3137             mWebTextView.setInPassword(nativeFocusCandidateIsPassword());
3138             if (null == text) {
3139                 mWebTextView.setText("", 0, 0);
3140                 if (DebugFlags.WEB_VIEW) {
3141                     Log.v(LOGTAG, "rebuildWebTextView null == text");
3142                 }
3143             } else {
3144                 // Change to true to enable the old style behavior, where
3145                 // entering a textfield/textarea always set the selection to the
3146                 // whole field.  This was desirable for the case where the user
3147                 // intends to scroll past the field using the trackball.
3148                 // However, it causes a problem when replying to emails - the
3149                 // user expects the cursor to be at the beginning of the
3150                 // textarea.  Testing out a new behavior, where textfields set
3151                 // selection at the end, and textareas at the beginning.
3152                 if (false) {
3153                     mWebTextView.setText(text, 0, text.length());
3154                 } else if (isTextField) {
3155                     int length = text.length();
3156                     mWebTextView.setText(text, length, length);
3157                     if (DebugFlags.WEB_VIEW) {
3158                         Log.v(LOGTAG, "rebuildWebTextView length=" + length);
3159                     }
3160                 } else {
3161                     mWebTextView.setText(text, 0, 0);
3162                     if (DebugFlags.WEB_VIEW) {
3163                         Log.v(LOGTAG, "rebuildWebTextView !isTextField");
3164                     }
3165                 }
3166             }
3167             mWebTextView.requestFocus();
3168         }
3169     }
3170 
3171     /*
3172      * This class requests an Adapter for the WebTextView which shows past
3173      * entries stored in the database.  It is a Runnable so that it can be done
3174      * in its own thread, without slowing down the UI.
3175      */
3176     private class RequestFormData implements Runnable {
3177         private String mName;
3178         private String mUrl;
3179         private Message mUpdateMessage;
3180 
RequestFormData(String name, String url, Message msg)3181         public RequestFormData(String name, String url, Message msg) {
3182             mName = name;
3183             mUrl = url;
3184             mUpdateMessage = msg;
3185         }
3186 
run()3187         public void run() {
3188             ArrayList<String> pastEntries = mDatabase.getFormData(mUrl, mName);
3189             if (pastEntries.size() > 0) {
3190                 AutoCompleteAdapter adapter = new
3191                         AutoCompleteAdapter(mContext, pastEntries);
3192                 mUpdateMessage.obj = adapter;
3193                 mUpdateMessage.sendToTarget();
3194             }
3195         }
3196     }
3197 
3198     // This is used to determine long press with the center key.  Does not
3199     // affect long press with the trackball/touch.
3200     private boolean mGotCenterDown = false;
3201 
3202     @Override
onKeyDown(int keyCode, KeyEvent event)3203     public boolean onKeyDown(int keyCode, KeyEvent event) {
3204         if (DebugFlags.WEB_VIEW) {
3205             Log.v(LOGTAG, "keyDown at " + System.currentTimeMillis()
3206                     + ", " + event + ", unicode=" + event.getUnicodeChar());
3207         }
3208 
3209         if (mNativeClass == 0) {
3210             return false;
3211         }
3212 
3213         // do this hack up front, so it always works, regardless of touch-mode
3214         if (AUTO_REDRAW_HACK && (keyCode == KeyEvent.KEYCODE_CALL)) {
3215             mAutoRedraw = !mAutoRedraw;
3216             if (mAutoRedraw) {
3217                 invalidate();
3218             }
3219             return true;
3220         }
3221 
3222         // Bubble up the key event if
3223         // 1. it is a system key; or
3224         // 2. the host application wants to handle it;
3225         if (event.isSystem()
3226                 || mCallbackProxy.uiOverrideKeyEvent(event)) {
3227             return false;
3228         }
3229 
3230         if (mShiftIsPressed == false && nativeCursorWantsKeyEvents() == false
3231                 && (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
3232                 || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT)) {
3233             mExtendSelection = false;
3234             mShiftIsPressed = true;
3235             if (nativeHasCursorNode()) {
3236                 Rect rect = nativeCursorNodeBounds();
3237                 mSelectX = contentToViewX(rect.left);
3238                 mSelectY = contentToViewY(rect.top);
3239             } else {
3240                 mSelectX = mScrollX + (int) mLastTouchX;
3241                 mSelectY = mScrollY + (int) mLastTouchY;
3242             }
3243             nativeHideCursor();
3244        }
3245 
3246         if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
3247                 && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
3248             // always handle the navigation keys in the UI thread
3249             switchOutDrawHistory();
3250             if (navHandledKey(keyCode, 1, false, event.getEventTime(), false)) {
3251                 playSoundEffect(keyCodeToSoundsEffect(keyCode));
3252                 return true;
3253             }
3254             // Bubble up the key event as WebView doesn't handle it
3255             return false;
3256         }
3257 
3258         if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
3259             switchOutDrawHistory();
3260             if (event.getRepeatCount() == 0) {
3261                 mGotCenterDown = true;
3262                 mPrivateHandler.sendMessageDelayed(mPrivateHandler
3263                         .obtainMessage(LONG_PRESS_CENTER), LONG_PRESS_TIMEOUT);
3264                 // Already checked mNativeClass, so we do not need to check it
3265                 // again.
3266                 nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true);
3267                 return true;
3268             }
3269             // Bubble up the key event as WebView doesn't handle it
3270             return false;
3271         }
3272 
3273         if (keyCode != KeyEvent.KEYCODE_SHIFT_LEFT
3274                 && keyCode != KeyEvent.KEYCODE_SHIFT_RIGHT) {
3275             // turn off copy select if a shift-key combo is pressed
3276             mExtendSelection = mShiftIsPressed = false;
3277             if (mTouchMode == TOUCH_SELECT_MODE) {
3278                 mTouchMode = TOUCH_INIT_MODE;
3279             }
3280         }
3281 
3282         if (getSettings().getNavDump()) {
3283             switch (keyCode) {
3284                 case KeyEvent.KEYCODE_4:
3285                     // "/data/data/com.android.browser/displayTree.txt"
3286                     nativeDumpDisplayTree(getUrl());
3287                     break;
3288                 case KeyEvent.KEYCODE_5:
3289                 case KeyEvent.KEYCODE_6:
3290                     // 5: dump the dom tree to the file
3291                     // "/data/data/com.android.browser/domTree.txt"
3292                     // 6: dump the dom tree to the adb log
3293                     mWebViewCore.sendMessage(EventHub.DUMP_DOMTREE,
3294                             (keyCode == KeyEvent.KEYCODE_5) ? 1 : 0, 0);
3295                     break;
3296                 case KeyEvent.KEYCODE_7:
3297                 case KeyEvent.KEYCODE_8:
3298                     // 7: dump the render tree to the file
3299                     // "/data/data/com.android.browser/renderTree.txt"
3300                     // 8: dump the render tree to the adb log
3301                     mWebViewCore.sendMessage(EventHub.DUMP_RENDERTREE,
3302                             (keyCode == KeyEvent.KEYCODE_7) ? 1 : 0, 0);
3303                     break;
3304                 case KeyEvent.KEYCODE_9:
3305                     nativeInstrumentReport();
3306                     return true;
3307             }
3308         }
3309 
3310         if (nativeCursorIsPlugin()) {
3311             nativeUpdatePluginReceivesEvents();
3312             invalidate();
3313         } else if (nativeCursorIsTextInput()) {
3314             // This message will put the node in focus, for the DOM's notion
3315             // of focus, and make the focuscontroller active
3316             mWebViewCore.sendMessage(EventHub.CLICK, nativeCursorFramePointer(),
3317                     nativeCursorNodePointer());
3318             // This will bring up the WebTextView and put it in focus, for
3319             // our view system's notion of focus
3320             rebuildWebTextView();
3321             // Now we need to pass the event to it
3322             return mWebTextView.onKeyDown(keyCode, event);
3323         } else if (nativeHasFocusNode()) {
3324             // In this case, the cursor is not on a text input, but the focus
3325             // might be.  Check it, and if so, hand over to the WebTextView.
3326             rebuildWebTextView();
3327             if (inEditingMode()) {
3328                 return mWebTextView.onKeyDown(keyCode, event);
3329             }
3330         }
3331 
3332         // TODO: should we pass all the keys to DOM or check the meta tag
3333         if (nativeCursorWantsKeyEvents() || true) {
3334             // pass the key to DOM
3335             mWebViewCore.sendMessage(EventHub.KEY_DOWN, event);
3336             // return true as DOM handles the key
3337             return true;
3338         }
3339 
3340         // Bubble up the key event as WebView doesn't handle it
3341         return false;
3342     }
3343 
3344     @Override
onKeyUp(int keyCode, KeyEvent event)3345     public boolean onKeyUp(int keyCode, KeyEvent event) {
3346         if (DebugFlags.WEB_VIEW) {
3347             Log.v(LOGTAG, "keyUp at " + System.currentTimeMillis()
3348                     + ", " + event + ", unicode=" + event.getUnicodeChar());
3349         }
3350 
3351         if (mNativeClass == 0) {
3352             return false;
3353         }
3354 
3355         // special CALL handling when cursor node's href is "tel:XXX"
3356         if (keyCode == KeyEvent.KEYCODE_CALL && nativeHasCursorNode()) {
3357             String text = nativeCursorText();
3358             if (!nativeCursorIsTextInput() && text != null
3359                     && text.startsWith(SCHEME_TEL)) {
3360                 Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(text));
3361                 getContext().startActivity(intent);
3362                 return true;
3363             }
3364         }
3365 
3366         // Bubble up the key event if
3367         // 1. it is a system key; or
3368         // 2. the host application wants to handle it;
3369         if (event.isSystem() || mCallbackProxy.uiOverrideKeyEvent(event)) {
3370             return false;
3371         }
3372 
3373         if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
3374                 || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
3375             if (commitCopy()) {
3376                 return true;
3377             }
3378         }
3379 
3380         if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
3381                 && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
3382             // always handle the navigation keys in the UI thread
3383             // Bubble up the key event as WebView doesn't handle it
3384             return false;
3385         }
3386 
3387         if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
3388             // remove the long press message first
3389             mPrivateHandler.removeMessages(LONG_PRESS_CENTER);
3390             mGotCenterDown = false;
3391 
3392             if (mShiftIsPressed) {
3393                 return false;
3394             }
3395 
3396             // perform the single click
3397             Rect visibleRect = sendOurVisibleRect();
3398             // Note that sendOurVisibleRect calls viewToContent, so the
3399             // coordinates should be in content coordinates.
3400             if (!nativeCursorIntersects(visibleRect)) {
3401                 return false;
3402             }
3403             nativeSetFollowedLink(true);
3404             nativeUpdatePluginReceivesEvents();
3405             WebViewCore.CursorData data = cursorData();
3406             mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, data);
3407             playSoundEffect(SoundEffectConstants.CLICK);
3408             boolean isTextInput = nativeCursorIsTextInput();
3409             if (isTextInput || !mCallbackProxy.uiOverrideUrlLoading(
3410                         nativeCursorText())) {
3411                 mWebViewCore.sendMessage(EventHub.CLICK, data.mFrame,
3412                         nativeCursorNodePointer());
3413             }
3414             if (isTextInput) {
3415                 rebuildWebTextView();
3416                 displaySoftKeyboard(true);
3417             }
3418             return true;
3419         }
3420 
3421         // TODO: should we pass all the keys to DOM or check the meta tag
3422         if (nativeCursorWantsKeyEvents() || true) {
3423             // pass the key to DOM
3424             mWebViewCore.sendMessage(EventHub.KEY_UP, event);
3425             // return true as DOM handles the key
3426             return true;
3427         }
3428 
3429         // Bubble up the key event as WebView doesn't handle it
3430         return false;
3431     }
3432 
3433     /**
3434      * @hide
3435      */
emulateShiftHeld()3436     public void emulateShiftHeld() {
3437         if (0 == mNativeClass) return; // client isn't initialized
3438         mExtendSelection = false;
3439         mShiftIsPressed = true;
3440         nativeHideCursor();
3441     }
3442 
commitCopy()3443     private boolean commitCopy() {
3444         boolean copiedSomething = false;
3445         if (mExtendSelection) {
3446             // copy region so core operates on copy without touching orig.
3447             Region selection = new Region(nativeGetSelection());
3448             if (selection.isEmpty() == false) {
3449                 Toast.makeText(mContext
3450                         , com.android.internal.R.string.text_copied
3451                         , Toast.LENGTH_SHORT).show();
3452                 mWebViewCore.sendMessage(EventHub.GET_SELECTION, selection);
3453                 copiedSomething = true;
3454             }
3455             mExtendSelection = false;
3456         }
3457         mShiftIsPressed = false;
3458         if (mTouchMode == TOUCH_SELECT_MODE) {
3459             mTouchMode = TOUCH_INIT_MODE;
3460         }
3461         return copiedSomething;
3462     }
3463 
3464     // Set this as a hierarchy change listener so we can know when this view
3465     // is removed and still have access to our parent.
3466     @Override
onAttachedToWindow()3467     protected void onAttachedToWindow() {
3468         super.onAttachedToWindow();
3469         ViewParent parent = getParent();
3470         if (parent instanceof ViewGroup) {
3471             ViewGroup p = (ViewGroup) parent;
3472             p.setOnHierarchyChangeListener(this);
3473         }
3474     }
3475 
3476     @Override
onDetachedFromWindow()3477     protected void onDetachedFromWindow() {
3478         super.onDetachedFromWindow();
3479         ViewParent parent = getParent();
3480         if (parent instanceof ViewGroup) {
3481             ViewGroup p = (ViewGroup) parent;
3482             p.setOnHierarchyChangeListener(null);
3483         }
3484 
3485         // Clean up the zoom controller
3486         mZoomButtonsController.setVisible(false);
3487     }
3488 
3489     // Implementation for OnHierarchyChangeListener
onChildViewAdded(View parent, View child)3490     public void onChildViewAdded(View parent, View child) {}
3491 
onChildViewRemoved(View p, View child)3492     public void onChildViewRemoved(View p, View child) {
3493         if (child == this) {
3494             clearTextEntry();
3495         }
3496     }
3497 
3498     /**
3499      * @deprecated WebView should not have implemented
3500      * ViewTreeObserver.OnGlobalFocusChangeListener.  This method
3501      * does nothing now.
3502      */
3503     @Deprecated
onGlobalFocusChanged(View oldFocus, View newFocus)3504     public void onGlobalFocusChanged(View oldFocus, View newFocus) {
3505     }
3506 
3507     // To avoid drawing the cursor ring, and remove the TextView when our window
3508     // loses focus.
3509     @Override
onWindowFocusChanged(boolean hasWindowFocus)3510     public void onWindowFocusChanged(boolean hasWindowFocus) {
3511         if (hasWindowFocus) {
3512             if (hasFocus()) {
3513                 // If our window regained focus, and we have focus, then begin
3514                 // drawing the cursor ring
3515                 mDrawCursorRing = true;
3516                 if (mNativeClass != 0) {
3517                     nativeRecordButtons(true, false, true);
3518                     if (inEditingMode()) {
3519                         mWebViewCore.sendMessage(EventHub.SET_ACTIVE, 1, 0);
3520                     }
3521                 }
3522             } else {
3523                 // If our window gained focus, but we do not have it, do not
3524                 // draw the cursor ring.
3525                 mDrawCursorRing = false;
3526                 // We do not call nativeRecordButtons here because we assume
3527                 // that when we lost focus, or window focus, it got called with
3528                 // false for the first parameter
3529             }
3530         } else {
3531             if (getSettings().getBuiltInZoomControls() && !mZoomButtonsController.isVisible()) {
3532                 /*
3533                  * The zoom controls come in their own window, so our window
3534                  * loses focus. Our policy is to not draw the cursor ring if
3535                  * our window is not focused, but this is an exception since
3536                  * the user can still navigate the web page with the zoom
3537                  * controls showing.
3538                  */
3539                 // If our window has lost focus, stop drawing the cursor ring
3540                 mDrawCursorRing = false;
3541             }
3542             mGotKeyDown = false;
3543             mShiftIsPressed = false;
3544             if (mNativeClass != 0) {
3545                 nativeRecordButtons(false, false, true);
3546             }
3547             setFocusControllerInactive();
3548         }
3549         invalidate();
3550         super.onWindowFocusChanged(hasWindowFocus);
3551     }
3552 
3553     /*
3554      * Pass a message to WebCore Thread, telling the WebCore::Page's
3555      * FocusController to be  "inactive" so that it will
3556      * not draw the blinking cursor.  It gets set to "active" to draw the cursor
3557      * in WebViewCore.cpp, when the WebCore thread receives key events/clicks.
3558      */
setFocusControllerInactive()3559     /* package */ void setFocusControllerInactive() {
3560         // Do not need to also check whether mWebViewCore is null, because
3561         // mNativeClass is only set if mWebViewCore is non null
3562         if (mNativeClass == 0) return;
3563         mWebViewCore.sendMessage(EventHub.SET_ACTIVE, 0, 0);
3564     }
3565 
3566     @Override
onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)3567     protected void onFocusChanged(boolean focused, int direction,
3568             Rect previouslyFocusedRect) {
3569         if (DebugFlags.WEB_VIEW) {
3570             Log.v(LOGTAG, "MT focusChanged " + focused + ", " + direction);
3571         }
3572         if (focused) {
3573             // When we regain focus, if we have window focus, resume drawing
3574             // the cursor ring
3575             if (hasWindowFocus()) {
3576                 mDrawCursorRing = true;
3577                 if (mNativeClass != 0) {
3578                     nativeRecordButtons(true, false, true);
3579                 }
3580             //} else {
3581                 // The WebView has gained focus while we do not have
3582                 // windowfocus.  When our window lost focus, we should have
3583                 // called nativeRecordButtons(false...)
3584             }
3585         } else {
3586             // When we lost focus, unless focus went to the TextView (which is
3587             // true if we are in editing mode), stop drawing the cursor ring.
3588             if (!inEditingMode()) {
3589                 mDrawCursorRing = false;
3590                 if (mNativeClass != 0) {
3591                     nativeRecordButtons(false, false, true);
3592                 }
3593                 setFocusControllerInactive();
3594             }
3595             mGotKeyDown = false;
3596         }
3597 
3598         super.onFocusChanged(focused, direction, previouslyFocusedRect);
3599     }
3600 
3601     @Override
onSizeChanged(int w, int h, int ow, int oh)3602     protected void onSizeChanged(int w, int h, int ow, int oh) {
3603         super.onSizeChanged(w, h, ow, oh);
3604         // Center zooming to the center of the screen.
3605         if (mZoomScale == 0) { // unless we're already zooming
3606             mZoomCenterX = getViewWidth() * .5f;
3607             mZoomCenterY = getViewHeight() * .5f;
3608         }
3609 
3610         // update mMinZoomScale if the minimum zoom scale is not fixed
3611         if (!mMinZoomScaleFixed) {
3612             // when change from narrow screen to wide screen, the new viewWidth
3613             // can be wider than the old content width. We limit the minimum
3614             // scale to 1.0f. The proper minimum scale will be calculated when
3615             // the new picture shows up.
3616             mMinZoomScale = Math.min(1.0f, (float) getViewWidth()
3617                     / (mDrawHistory ? mHistoryPicture.getWidth()
3618                             : mZoomOverviewWidth));
3619             if (mInitialScaleInPercent > 0) {
3620                 // limit the minZoomScale to the initialScale if it is set
3621                 float initialScale = mInitialScaleInPercent / 100.0f;
3622                 if (mMinZoomScale > initialScale) {
3623                     mMinZoomScale = initialScale;
3624                 }
3625             }
3626         }
3627 
3628         // we always force, in case our height changed, in which case we still
3629         // want to send the notification over to webkit
3630         setNewZoomScale(mActualScale, true);
3631     }
3632 
3633     @Override
onScrollChanged(int l, int t, int oldl, int oldt)3634     protected void onScrollChanged(int l, int t, int oldl, int oldt) {
3635         super.onScrollChanged(l, t, oldl, oldt);
3636 
3637         sendOurVisibleRect();
3638     }
3639 
3640 
3641     @Override
dispatchKeyEvent(KeyEvent event)3642     public boolean dispatchKeyEvent(KeyEvent event) {
3643         boolean dispatch = true;
3644 
3645         if (!inEditingMode()) {
3646             if (event.getAction() == KeyEvent.ACTION_DOWN) {
3647                 mGotKeyDown = true;
3648             } else {
3649                 if (!mGotKeyDown) {
3650                     /*
3651                      * We got a key up for which we were not the recipient of
3652                      * the original key down. Don't give it to the view.
3653                      */
3654                     dispatch = false;
3655                 }
3656                 mGotKeyDown = false;
3657             }
3658         }
3659 
3660         if (dispatch) {
3661             return super.dispatchKeyEvent(event);
3662         } else {
3663             // We didn't dispatch, so let something else handle the key
3664             return false;
3665         }
3666     }
3667 
3668     // Here are the snap align logic:
3669     // 1. If it starts nearly horizontally or vertically, snap align;
3670     // 2. If there is a dramitic direction change, let it go;
3671     // 3. If there is a same direction back and forth, lock it.
3672 
3673     // adjustable parameters
3674     private int mMinLockSnapReverseDistance;
3675     private static final float MAX_SLOPE_FOR_DIAG = 1.5f;
3676     private static final int MIN_BREAK_SNAP_CROSS_DISTANCE = 80;
3677 
3678     @Override
onTouchEvent(MotionEvent ev)3679     public boolean onTouchEvent(MotionEvent ev) {
3680         if (mNativeClass == 0 || !isClickable() || !isLongClickable()) {
3681             return false;
3682         }
3683 
3684         if (DebugFlags.WEB_VIEW) {
3685             Log.v(LOGTAG, ev + " at " + ev.getEventTime() + " mTouchMode="
3686                     + mTouchMode);
3687         }
3688 
3689         int action = ev.getAction();
3690         float x = ev.getX();
3691         float y = ev.getY();
3692         long eventTime = ev.getEventTime();
3693 
3694         // Due to the touch screen edge effect, a touch closer to the edge
3695         // always snapped to the edge. As getViewWidth() can be different from
3696         // getWidth() due to the scrollbar, adjusting the point to match
3697         // getViewWidth(). Same applied to the height.
3698         if (x > getViewWidth() - 1) {
3699             x = getViewWidth() - 1;
3700         }
3701         if (y > getViewHeightWithTitle() - 1) {
3702             y = getViewHeightWithTitle() - 1;
3703         }
3704 
3705         // pass the touch events from UI thread to WebCore thread
3706         if (mForwardTouchEvents && (action != MotionEvent.ACTION_MOVE
3707                 || eventTime - mLastSentTouchTime > TOUCH_SENT_INTERVAL)) {
3708             WebViewCore.TouchEventData ted = new WebViewCore.TouchEventData();
3709             ted.mAction = action;
3710             ted.mX = viewToContentX((int) x + mScrollX);
3711             ted.mY = viewToContentY((int) y + mScrollY);
3712             mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
3713             mLastSentTouchTime = eventTime;
3714         }
3715 
3716         int deltaX = (int) (mLastTouchX - x);
3717         int deltaY = (int) (mLastTouchY - y);
3718 
3719         switch (action) {
3720             case MotionEvent.ACTION_DOWN: {
3721                 mPreventDrag = PREVENT_DRAG_NO;
3722                 if (!mScroller.isFinished()) {
3723                     // stop the current scroll animation, but if this is
3724                     // the start of a fling, allow it to add to the current
3725                     // fling's velocity
3726                     mScroller.abortAnimation();
3727                     mTouchMode = TOUCH_DRAG_START_MODE;
3728                     mPrivateHandler.removeMessages(RESUME_WEBCORE_UPDATE);
3729                 } else if (mShiftIsPressed) {
3730                     mSelectX = mScrollX + (int) x;
3731                     mSelectY = mScrollY + (int) y;
3732                     mTouchMode = TOUCH_SELECT_MODE;
3733                     if (DebugFlags.WEB_VIEW) {
3734                         Log.v(LOGTAG, "select=" + mSelectX + "," + mSelectY);
3735                     }
3736                     nativeMoveSelection(viewToContentX(mSelectX),
3737                             viewToContentY(mSelectY), false);
3738                     mTouchSelection = mExtendSelection = true;
3739                 } else if (mPrivateHandler.hasMessages(RELEASE_SINGLE_TAP)) {
3740                     mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP);
3741                     if (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare) {
3742                         mTouchMode = TOUCH_DOUBLE_TAP_MODE;
3743                     } else {
3744                         // commit the short press action for the previous tap
3745                         doShortPress();
3746                         // continue, mTouchMode should be still TOUCH_INIT_MODE
3747                     }
3748                 } else {
3749                     mTouchMode = TOUCH_INIT_MODE;
3750                     mPreventDrag = mForwardTouchEvents ? PREVENT_DRAG_MAYBE_YES
3751                             : PREVENT_DRAG_NO;
3752                     mWebViewCore.sendMessage(
3753                             EventHub.UPDATE_FRAME_CACHE_IF_LOADING);
3754                     if (mLogEvent && eventTime - mLastTouchUpTime < 1000) {
3755                         EventLog.writeEvent(EVENT_LOG_DOUBLE_TAP_DURATION,
3756                                 (eventTime - mLastTouchUpTime), eventTime);
3757                     }
3758                 }
3759                 // Trigger the link
3760                 if (mTouchMode == TOUCH_INIT_MODE
3761                         || mTouchMode == TOUCH_DOUBLE_TAP_MODE) {
3762                     mPrivateHandler.sendMessageDelayed(mPrivateHandler
3763                             .obtainMessage(SWITCH_TO_SHORTPRESS), TAP_TIMEOUT);
3764                 }
3765                 // Remember where the motion event started
3766                 mLastTouchX = x;
3767                 mLastTouchY = y;
3768                 mLastTouchTime = eventTime;
3769                 mVelocityTracker = VelocityTracker.obtain();
3770                 mSnapScrollMode = SNAP_NONE;
3771                 break;
3772             }
3773             case MotionEvent.ACTION_MOVE: {
3774                 if (mTouchMode == TOUCH_DONE_MODE) {
3775                     // no dragging during scroll zoom animation
3776                     break;
3777                 }
3778                 mVelocityTracker.addMovement(ev);
3779 
3780                 if (mTouchMode != TOUCH_DRAG_MODE) {
3781                     if (mTouchMode == TOUCH_SELECT_MODE) {
3782                         mSelectX = mScrollX + (int) x;
3783                         mSelectY = mScrollY + (int) y;
3784                         if (DebugFlags.WEB_VIEW) {
3785                             Log.v(LOGTAG, "xtend=" + mSelectX + "," + mSelectY);
3786                         }
3787                         nativeMoveSelection(viewToContentX(mSelectX),
3788                                viewToContentY(mSelectY), true);
3789                         invalidate();
3790                         break;
3791                     }
3792                     if ((deltaX * deltaX + deltaY * deltaY) < mTouchSlopSquare) {
3793                         break;
3794                     }
3795                     if (mPreventDrag == PREVENT_DRAG_MAYBE_YES) {
3796                         // track mLastTouchTime as we may need to do fling at
3797                         // ACTION_UP
3798                         mLastTouchTime = eventTime;
3799                         break;
3800                     }
3801                     if (mTouchMode == TOUCH_SHORTPRESS_MODE
3802                             || mTouchMode == TOUCH_SHORTPRESS_START_MODE) {
3803                         mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
3804                     } else if (mTouchMode == TOUCH_INIT_MODE
3805                             || mTouchMode == TOUCH_DOUBLE_TAP_MODE) {
3806                         mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
3807                     }
3808 
3809                     // if it starts nearly horizontal or vertical, enforce it
3810                     int ax = Math.abs(deltaX);
3811                     int ay = Math.abs(deltaY);
3812                     if (ax > MAX_SLOPE_FOR_DIAG * ay) {
3813                         mSnapScrollMode = SNAP_X;
3814                         mSnapPositive = deltaX > 0;
3815                     } else if (ay > MAX_SLOPE_FOR_DIAG * ax) {
3816                         mSnapScrollMode = SNAP_Y;
3817                         mSnapPositive = deltaY > 0;
3818                     }
3819 
3820                     mTouchMode = TOUCH_DRAG_MODE;
3821                     WebViewCore.pauseUpdate(mWebViewCore);
3822                     if (!mDragFromTextInput) {
3823                         nativeHideCursor();
3824                     }
3825                     WebSettings settings = getSettings();
3826                     if (settings.supportZoom()
3827                             && settings.getBuiltInZoomControls()
3828                             && !mZoomButtonsController.isVisible()
3829                             && mMinZoomScale < mMaxZoomScale) {
3830                         mZoomButtonsController.setVisible(true);
3831                         int count = settings.getDoubleTapToastCount();
3832                         if (mInZoomOverview && count > 0) {
3833                             settings.setDoubleTapToastCount(--count);
3834                             Toast.makeText(mContext,
3835                                     com.android.internal.R.string.double_tap_toast,
3836                                     Toast.LENGTH_LONG).show();
3837                         }
3838                     }
3839                 }
3840 
3841                 // do pan
3842                 int newScrollX = pinLocX(mScrollX + deltaX);
3843                 deltaX = newScrollX - mScrollX;
3844                 int newScrollY = pinLocY(mScrollY + deltaY);
3845                 deltaY = newScrollY - mScrollY;
3846                 boolean done = false;
3847                 if (deltaX == 0 && deltaY == 0) {
3848                     done = true;
3849                 } else {
3850                     if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_Y) {
3851                         int ax = Math.abs(deltaX);
3852                         int ay = Math.abs(deltaY);
3853                         if (mSnapScrollMode == SNAP_X) {
3854                             // radical change means getting out of snap mode
3855                             if (ay > MAX_SLOPE_FOR_DIAG * ax
3856                                     && ay > MIN_BREAK_SNAP_CROSS_DISTANCE) {
3857                                 mSnapScrollMode = SNAP_NONE;
3858                             }
3859                             // reverse direction means lock in the snap mode
3860                             if ((ax > MAX_SLOPE_FOR_DIAG * ay) &&
3861                                     ((mSnapPositive &&
3862                                     deltaX < -mMinLockSnapReverseDistance)
3863                                     || (!mSnapPositive &&
3864                                     deltaX > mMinLockSnapReverseDistance))) {
3865                                 mSnapScrollMode = SNAP_X_LOCK;
3866                             }
3867                         } else {
3868                             // radical change means getting out of snap mode
3869                             if ((ax > MAX_SLOPE_FOR_DIAG * ay)
3870                                     && ax > MIN_BREAK_SNAP_CROSS_DISTANCE) {
3871                                 mSnapScrollMode = SNAP_NONE;
3872                             }
3873                             // reverse direction means lock in the snap mode
3874                             if ((ay > MAX_SLOPE_FOR_DIAG * ax) &&
3875                                     ((mSnapPositive &&
3876                                     deltaY < -mMinLockSnapReverseDistance)
3877                                     || (!mSnapPositive &&
3878                                     deltaY > mMinLockSnapReverseDistance))) {
3879                                 mSnapScrollMode = SNAP_Y_LOCK;
3880                             }
3881                         }
3882                     }
3883 
3884                     if (mSnapScrollMode == SNAP_X
3885                             || mSnapScrollMode == SNAP_X_LOCK) {
3886                         if (deltaX == 0) {
3887                             // keep the scrollbar on the screen even there is no
3888                             // scroll
3889                             awakenScrollBars(ViewConfiguration
3890                                     .getScrollDefaultDelay(), false);
3891                         } else {
3892                             scrollBy(deltaX, 0);
3893                         }
3894                         mLastTouchX = x;
3895                     } else if (mSnapScrollMode == SNAP_Y
3896                             || mSnapScrollMode == SNAP_Y_LOCK) {
3897                         if (deltaY == 0) {
3898                             // keep the scrollbar on the screen even there is no
3899                             // scroll
3900                             awakenScrollBars(ViewConfiguration
3901                                     .getScrollDefaultDelay(), false);
3902                         } else {
3903                             scrollBy(0, deltaY);
3904                         }
3905                         mLastTouchY = y;
3906                     } else {
3907                         scrollBy(deltaX, deltaY);
3908                         mLastTouchX = x;
3909                         mLastTouchY = y;
3910                     }
3911                     mLastTouchTime = eventTime;
3912                     mUserScroll = true;
3913                 }
3914 
3915                 if (!getSettings().getBuiltInZoomControls()) {
3916                     boolean showPlusMinus = mMinZoomScale < mMaxZoomScale;
3917                     if (mZoomControls != null && showPlusMinus) {
3918                         if (mZoomControls.getVisibility() == View.VISIBLE) {
3919                             mPrivateHandler.removeCallbacks(mZoomControlRunnable);
3920                         } else {
3921                             mZoomControls.show(showPlusMinus, false);
3922                         }
3923                         mPrivateHandler.postDelayed(mZoomControlRunnable,
3924                                 ZOOM_CONTROLS_TIMEOUT);
3925                     }
3926                 }
3927 
3928                 if (done) {
3929                     // keep the scrollbar on the screen even there is no scroll
3930                     awakenScrollBars(ViewConfiguration.getScrollDefaultDelay(),
3931                             false);
3932                     // return false to indicate that we can't pan out of the
3933                     // view space
3934                     return false;
3935                 }
3936                 break;
3937             }
3938             case MotionEvent.ACTION_UP: {
3939                 mLastTouchUpTime = eventTime;
3940                 switch (mTouchMode) {
3941                     case TOUCH_DOUBLE_TAP_MODE: // double tap
3942                         mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
3943                         mTouchMode = TOUCH_DONE_MODE;
3944                         doDoubleTap();
3945                         break;
3946                     case TOUCH_SELECT_MODE:
3947                         commitCopy();
3948                         mTouchSelection = false;
3949                         break;
3950                     case TOUCH_INIT_MODE: // tap
3951                     case TOUCH_SHORTPRESS_START_MODE:
3952                     case TOUCH_SHORTPRESS_MODE:
3953                         mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
3954                         mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
3955                         if ((deltaX * deltaX + deltaY * deltaY) > mTouchSlopSquare) {
3956                             Log.w(LOGTAG, "Miss a drag as we are waiting for" +
3957                                     " WebCore's response for touch down.");
3958                             if (computeHorizontalScrollExtent() < computeHorizontalScrollRange()
3959                                     || computeVerticalScrollExtent() < computeVerticalScrollRange()) {
3960                                 // we will not rewrite drag code here, but we
3961                                 // will try fling if it applies.
3962                                 WebViewCore.pauseUpdate(mWebViewCore);
3963                                 // fall through to TOUCH_DRAG_MODE
3964                             } else {
3965                                 break;
3966                             }
3967                         } else {
3968                             if (mPreventDrag == PREVENT_DRAG_MAYBE_YES) {
3969                                 // if mPreventDrag is not confirmed, treat it as
3970                                 // no so that it won't block tap or double tap.
3971                                 mPreventDrag = PREVENT_DRAG_NO;
3972                             }
3973                             if (mPreventDrag == PREVENT_DRAG_NO) {
3974                                 if (mTouchMode == TOUCH_INIT_MODE) {
3975                                     mPrivateHandler.sendMessageDelayed(
3976                                             mPrivateHandler.obtainMessage(
3977                                             RELEASE_SINGLE_TAP),
3978                                             ViewConfiguration.getDoubleTapTimeout());
3979                                 } else {
3980                                     mTouchMode = TOUCH_DONE_MODE;
3981                                     doShortPress();
3982                                 }
3983                             }
3984                             break;
3985                         }
3986                     case TOUCH_DRAG_MODE:
3987                         // redraw in high-quality, as we're done dragging
3988                         invalidate();
3989                         // if the user waits a while w/o moving before the
3990                         // up, we don't want to do a fling
3991                         if (eventTime - mLastTouchTime <= MIN_FLING_TIME) {
3992                             mVelocityTracker.addMovement(ev);
3993                             doFling();
3994                             break;
3995                         }
3996                         mLastVelocity = 0;
3997                         WebViewCore.resumeUpdate(mWebViewCore);
3998                         break;
3999                     case TOUCH_DRAG_START_MODE:
4000                     case TOUCH_DONE_MODE:
4001                         // do nothing
4002                         break;
4003                 }
4004                 // we also use mVelocityTracker == null to tell us that we are
4005                 // not "moving around", so we can take the slower/prettier
4006                 // mode in the drawing code
4007                 if (mVelocityTracker != null) {
4008                     mVelocityTracker.recycle();
4009                     mVelocityTracker = null;
4010                 }
4011                 break;
4012             }
4013             case MotionEvent.ACTION_CANCEL: {
4014                 // we also use mVelocityTracker == null to tell us that we are
4015                 // not "moving around", so we can take the slower/prettier
4016                 // mode in the drawing code
4017                 if (mVelocityTracker != null) {
4018                     mVelocityTracker.recycle();
4019                     mVelocityTracker = null;
4020                 }
4021                 if (mTouchMode == TOUCH_DRAG_MODE) {
4022                     WebViewCore.resumeUpdate(mWebViewCore);
4023                 }
4024                 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
4025                 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
4026                 mTouchMode = TOUCH_DONE_MODE;
4027                 nativeHideCursor();
4028                 break;
4029             }
4030         }
4031         return true;
4032     }
4033 
4034     private long mTrackballFirstTime = 0;
4035     private long mTrackballLastTime = 0;
4036     private float mTrackballRemainsX = 0.0f;
4037     private float mTrackballRemainsY = 0.0f;
4038     private int mTrackballXMove = 0;
4039     private int mTrackballYMove = 0;
4040     private boolean mExtendSelection = false;
4041     private boolean mTouchSelection = false;
4042     private static final int TRACKBALL_KEY_TIMEOUT = 1000;
4043     private static final int TRACKBALL_TIMEOUT = 200;
4044     private static final int TRACKBALL_WAIT = 100;
4045     private static final int TRACKBALL_SCALE = 400;
4046     private static final int TRACKBALL_SCROLL_COUNT = 5;
4047     private static final int TRACKBALL_MOVE_COUNT = 10;
4048     private static final int TRACKBALL_MULTIPLIER = 3;
4049     private static final int SELECT_CURSOR_OFFSET = 16;
4050     private int mSelectX = 0;
4051     private int mSelectY = 0;
4052     private boolean mShiftIsPressed = false;
4053     private boolean mTrackballDown = false;
4054     private long mTrackballUpTime = 0;
4055     private long mLastCursorTime = 0;
4056     private Rect mLastCursorBounds;
4057 
4058     // Set by default; BrowserActivity clears to interpret trackball data
4059     // directly for movement. Currently, the framework only passes
4060     // arrow key events, not trackball events, from one child to the next
4061     private boolean mMapTrackballToArrowKeys = true;
4062 
setMapTrackballToArrowKeys(boolean setMap)4063     public void setMapTrackballToArrowKeys(boolean setMap) {
4064         mMapTrackballToArrowKeys = setMap;
4065     }
4066 
resetTrackballTime()4067     void resetTrackballTime() {
4068         mTrackballLastTime = 0;
4069     }
4070 
4071     @Override
onTrackballEvent(MotionEvent ev)4072     public boolean onTrackballEvent(MotionEvent ev) {
4073         long time = ev.getEventTime();
4074         if ((ev.getMetaState() & KeyEvent.META_ALT_ON) != 0) {
4075             if (ev.getY() > 0) pageDown(true);
4076             if (ev.getY() < 0) pageUp(true);
4077             return true;
4078         }
4079         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
4080             if (mShiftIsPressed) {
4081                 return true; // discard press if copy in progress
4082             }
4083             mTrackballDown = true;
4084             if (mNativeClass == 0) {
4085                 return false;
4086             }
4087             nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true);
4088             if (time - mLastCursorTime <= TRACKBALL_TIMEOUT
4089                     && !mLastCursorBounds.equals(nativeGetCursorRingBounds())) {
4090                 nativeSelectBestAt(mLastCursorBounds);
4091             }
4092             if (DebugFlags.WEB_VIEW) {
4093                 Log.v(LOGTAG, "onTrackballEvent down ev=" + ev
4094                         + " time=" + time
4095                         + " mLastCursorTime=" + mLastCursorTime);
4096             }
4097             if (isInTouchMode()) requestFocusFromTouch();
4098             return false; // let common code in onKeyDown at it
4099         }
4100         if (ev.getAction() == MotionEvent.ACTION_UP) {
4101             // LONG_PRESS_CENTER is set in common onKeyDown
4102             mPrivateHandler.removeMessages(LONG_PRESS_CENTER);
4103             mTrackballDown = false;
4104             mTrackballUpTime = time;
4105             if (mShiftIsPressed) {
4106                 if (mExtendSelection) {
4107                     commitCopy();
4108                 } else {
4109                     mExtendSelection = true;
4110                 }
4111                 return true; // discard press if copy in progress
4112             }
4113             if (DebugFlags.WEB_VIEW) {
4114                 Log.v(LOGTAG, "onTrackballEvent up ev=" + ev
4115                         + " time=" + time
4116                 );
4117             }
4118             return false; // let common code in onKeyUp at it
4119         }
4120         if (mMapTrackballToArrowKeys && mShiftIsPressed == false) {
4121             if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent gmail quit");
4122             return false;
4123         }
4124         if (mTrackballDown) {
4125             if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent down quit");
4126             return true; // discard move if trackball is down
4127         }
4128         if (time - mTrackballUpTime < TRACKBALL_TIMEOUT) {
4129             if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent up timeout quit");
4130             return true;
4131         }
4132         // TODO: alternatively we can do panning as touch does
4133         switchOutDrawHistory();
4134         if (time - mTrackballLastTime > TRACKBALL_TIMEOUT) {
4135             if (DebugFlags.WEB_VIEW) {
4136                 Log.v(LOGTAG, "onTrackballEvent time="
4137                         + time + " last=" + mTrackballLastTime);
4138             }
4139             mTrackballFirstTime = time;
4140             mTrackballXMove = mTrackballYMove = 0;
4141         }
4142         mTrackballLastTime = time;
4143         if (DebugFlags.WEB_VIEW) {
4144             Log.v(LOGTAG, "onTrackballEvent ev=" + ev + " time=" + time);
4145         }
4146         mTrackballRemainsX += ev.getX();
4147         mTrackballRemainsY += ev.getY();
4148         doTrackball(time);
4149         return true;
4150     }
4151 
moveSelection(float xRate, float yRate)4152     void moveSelection(float xRate, float yRate) {
4153         if (mNativeClass == 0)
4154             return;
4155         int width = getViewWidth();
4156         int height = getViewHeight();
4157         mSelectX += scaleTrackballX(xRate, width);
4158         mSelectY += scaleTrackballY(yRate, height);
4159         int maxX = width + mScrollX;
4160         int maxY = height + mScrollY;
4161         mSelectX = Math.min(maxX, Math.max(mScrollX - SELECT_CURSOR_OFFSET
4162                 , mSelectX));
4163         mSelectY = Math.min(maxY, Math.max(mScrollY - SELECT_CURSOR_OFFSET
4164                 , mSelectY));
4165         if (DebugFlags.WEB_VIEW) {
4166             Log.v(LOGTAG, "moveSelection"
4167                     + " mSelectX=" + mSelectX
4168                     + " mSelectY=" + mSelectY
4169                     + " mScrollX=" + mScrollX
4170                     + " mScrollY=" + mScrollY
4171                     + " xRate=" + xRate
4172                     + " yRate=" + yRate
4173                     );
4174         }
4175         nativeMoveSelection(viewToContentX(mSelectX),
4176                 viewToContentY(mSelectY), mExtendSelection);
4177         int scrollX = mSelectX < mScrollX ? -SELECT_CURSOR_OFFSET
4178                 : mSelectX > maxX - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET
4179                 : 0;
4180         int scrollY = mSelectY < mScrollY ? -SELECT_CURSOR_OFFSET
4181                 : mSelectY > maxY - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET
4182                 : 0;
4183         pinScrollBy(scrollX, scrollY, true, 0);
4184         Rect select = new Rect(mSelectX, mSelectY, mSelectX + 1, mSelectY + 1);
4185         requestRectangleOnScreen(select);
4186         invalidate();
4187    }
4188 
scaleTrackballX(float xRate, int width)4189     private int scaleTrackballX(float xRate, int width) {
4190         int xMove = (int) (xRate / TRACKBALL_SCALE * width);
4191         int nextXMove = xMove;
4192         if (xMove > 0) {
4193             if (xMove > mTrackballXMove) {
4194                 xMove -= mTrackballXMove;
4195             }
4196         } else if (xMove < mTrackballXMove) {
4197             xMove -= mTrackballXMove;
4198         }
4199         mTrackballXMove = nextXMove;
4200         return xMove;
4201     }
4202 
scaleTrackballY(float yRate, int height)4203     private int scaleTrackballY(float yRate, int height) {
4204         int yMove = (int) (yRate / TRACKBALL_SCALE * height);
4205         int nextYMove = yMove;
4206         if (yMove > 0) {
4207             if (yMove > mTrackballYMove) {
4208                 yMove -= mTrackballYMove;
4209             }
4210         } else if (yMove < mTrackballYMove) {
4211             yMove -= mTrackballYMove;
4212         }
4213         mTrackballYMove = nextYMove;
4214         return yMove;
4215     }
4216 
keyCodeToSoundsEffect(int keyCode)4217     private int keyCodeToSoundsEffect(int keyCode) {
4218         switch(keyCode) {
4219             case KeyEvent.KEYCODE_DPAD_UP:
4220                 return SoundEffectConstants.NAVIGATION_UP;
4221             case KeyEvent.KEYCODE_DPAD_RIGHT:
4222                 return SoundEffectConstants.NAVIGATION_RIGHT;
4223             case KeyEvent.KEYCODE_DPAD_DOWN:
4224                 return SoundEffectConstants.NAVIGATION_DOWN;
4225             case KeyEvent.KEYCODE_DPAD_LEFT:
4226                 return SoundEffectConstants.NAVIGATION_LEFT;
4227         }
4228         throw new IllegalArgumentException("keyCode must be one of " +
4229                 "{KEYCODE_DPAD_UP, KEYCODE_DPAD_RIGHT, KEYCODE_DPAD_DOWN, " +
4230                 "KEYCODE_DPAD_LEFT}.");
4231     }
4232 
doTrackball(long time)4233     private void doTrackball(long time) {
4234         int elapsed = (int) (mTrackballLastTime - mTrackballFirstTime);
4235         if (elapsed == 0) {
4236             elapsed = TRACKBALL_TIMEOUT;
4237         }
4238         float xRate = mTrackballRemainsX * 1000 / elapsed;
4239         float yRate = mTrackballRemainsY * 1000 / elapsed;
4240         if (mShiftIsPressed) {
4241             moveSelection(xRate, yRate);
4242             mTrackballRemainsX = mTrackballRemainsY = 0;
4243             return;
4244         }
4245         float ax = Math.abs(xRate);
4246         float ay = Math.abs(yRate);
4247         float maxA = Math.max(ax, ay);
4248         if (DebugFlags.WEB_VIEW) {
4249             Log.v(LOGTAG, "doTrackball elapsed=" + elapsed
4250                     + " xRate=" + xRate
4251                     + " yRate=" + yRate
4252                     + " mTrackballRemainsX=" + mTrackballRemainsX
4253                     + " mTrackballRemainsY=" + mTrackballRemainsY);
4254         }
4255         int width = mContentWidth - getViewWidth();
4256         int height = mContentHeight - getViewHeight();
4257         if (width < 0) width = 0;
4258         if (height < 0) height = 0;
4259         ax = Math.abs(mTrackballRemainsX * TRACKBALL_MULTIPLIER);
4260         ay = Math.abs(mTrackballRemainsY * TRACKBALL_MULTIPLIER);
4261         maxA = Math.max(ax, ay);
4262         int count = Math.max(0, (int) maxA);
4263         int oldScrollX = mScrollX;
4264         int oldScrollY = mScrollY;
4265         if (count > 0) {
4266             int selectKeyCode = ax < ay ? mTrackballRemainsY < 0 ?
4267                     KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_DPAD_DOWN :
4268                     mTrackballRemainsX < 0 ? KeyEvent.KEYCODE_DPAD_LEFT :
4269                     KeyEvent.KEYCODE_DPAD_RIGHT;
4270             count = Math.min(count, TRACKBALL_MOVE_COUNT);
4271             if (DebugFlags.WEB_VIEW) {
4272                 Log.v(LOGTAG, "doTrackball keyCode=" + selectKeyCode
4273                         + " count=" + count
4274                         + " mTrackballRemainsX=" + mTrackballRemainsX
4275                         + " mTrackballRemainsY=" + mTrackballRemainsY);
4276             }
4277             if (navHandledKey(selectKeyCode, count, false, time, false)) {
4278                 playSoundEffect(keyCodeToSoundsEffect(selectKeyCode));
4279             }
4280             mTrackballRemainsX = mTrackballRemainsY = 0;
4281         }
4282         if (count >= TRACKBALL_SCROLL_COUNT) {
4283             int xMove = scaleTrackballX(xRate, width);
4284             int yMove = scaleTrackballY(yRate, height);
4285             if (DebugFlags.WEB_VIEW) {
4286                 Log.v(LOGTAG, "doTrackball pinScrollBy"
4287                         + " count=" + count
4288                         + " xMove=" + xMove + " yMove=" + yMove
4289                         + " mScrollX-oldScrollX=" + (mScrollX-oldScrollX)
4290                         + " mScrollY-oldScrollY=" + (mScrollY-oldScrollY)
4291                         );
4292             }
4293             if (Math.abs(mScrollX - oldScrollX) > Math.abs(xMove)) {
4294                 xMove = 0;
4295             }
4296             if (Math.abs(mScrollY - oldScrollY) > Math.abs(yMove)) {
4297                 yMove = 0;
4298             }
4299             if (xMove != 0 || yMove != 0) {
4300                 pinScrollBy(xMove, yMove, true, 0);
4301             }
4302             mUserScroll = true;
4303         }
4304     }
4305 
computeMaxScrollY()4306     private int computeMaxScrollY() {
4307         int maxContentH = computeVerticalScrollRange() + getTitleHeight();
4308         return Math.max(maxContentH - getViewHeightWithTitle(), getTitleHeight());
4309     }
4310 
flingScroll(int vx, int vy)4311     public void flingScroll(int vx, int vy) {
4312         int maxX = Math.max(computeHorizontalScrollRange() - getViewWidth(), 0);
4313         int maxY = computeMaxScrollY();
4314 
4315         mScroller.fling(mScrollX, mScrollY, vx, vy, 0, maxX, 0, maxY);
4316         invalidate();
4317     }
4318 
doFling()4319     private void doFling() {
4320         if (mVelocityTracker == null) {
4321             return;
4322         }
4323         int maxX = Math.max(computeHorizontalScrollRange() - getViewWidth(), 0);
4324         int maxY = computeMaxScrollY();
4325 
4326         mVelocityTracker.computeCurrentVelocity(1000, mMaximumFling);
4327         int vx = (int) mVelocityTracker.getXVelocity();
4328         int vy = (int) mVelocityTracker.getYVelocity();
4329 
4330         if (mSnapScrollMode != SNAP_NONE) {
4331             if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_X_LOCK) {
4332                 vy = 0;
4333             } else {
4334                 vx = 0;
4335             }
4336         }
4337 
4338         if (true /* EMG release: make our fling more like Maps' */) {
4339             // maps cuts their velocity in half
4340             vx = vx * 3 / 4;
4341             vy = vy * 3 / 4;
4342         }
4343         if ((maxX == 0 && vy == 0) || (maxY == 0 && vx == 0)) {
4344             WebViewCore.resumeUpdate(mWebViewCore);
4345             return;
4346         }
4347         float currentVelocity = mScroller.getCurrVelocity();
4348         if (mLastVelocity > 0 && currentVelocity > 0) {
4349             float deltaR = (float) (Math.abs(Math.atan2(mLastVelY, mLastVelX)
4350                     - Math.atan2(vy, vx)));
4351             final float circle = (float) (Math.PI) * 2.0f;
4352             if (deltaR > circle * 0.9f || deltaR < circle * 0.1f) {
4353                 vx += currentVelocity * mLastVelX / mLastVelocity;
4354                 vy += currentVelocity * mLastVelY / mLastVelocity;
4355                 if (DebugFlags.WEB_VIEW) {
4356                     Log.v(LOGTAG, "doFling vx= " + vx + " vy=" + vy);
4357                 }
4358             } else if (DebugFlags.WEB_VIEW) {
4359                 Log.v(LOGTAG, "doFling missed " + deltaR / circle);
4360             }
4361         } else if (DebugFlags.WEB_VIEW) {
4362             Log.v(LOGTAG, "doFling start last=" + mLastVelocity
4363                     + " current=" + currentVelocity
4364                     + " vx=" + vx + " vy=" + vy
4365                     + " maxX=" + maxX + " maxY=" + maxY
4366                     + " mScrollX=" + mScrollX + " mScrollY=" + mScrollY);
4367         }
4368         mLastVelX = vx;
4369         mLastVelY = vy;
4370         mLastVelocity = (float) Math.hypot(vx, vy);
4371 
4372         mScroller.fling(mScrollX, mScrollY, -vx, -vy, 0, maxX, 0, maxY);
4373         // TODO: duration is calculated based on velocity, if the range is
4374         // small, the animation will stop before duration is up. We may
4375         // want to calculate how long the animation is going to run to precisely
4376         // resume the webcore update.
4377         final int time = mScroller.getDuration();
4378         mPrivateHandler.sendEmptyMessageDelayed(RESUME_WEBCORE_UPDATE, time);
4379         awakenScrollBars(time);
4380         invalidate();
4381     }
4382 
zoomWithPreview(float scale)4383     private boolean zoomWithPreview(float scale) {
4384         float oldScale = mActualScale;
4385         mInitialScrollX = mScrollX;
4386         mInitialScrollY = mScrollY;
4387 
4388         // snap to DEFAULT_SCALE if it is close
4389         if (scale > (mDefaultScale - 0.05) && scale < (mDefaultScale + 0.05)) {
4390             scale = mDefaultScale;
4391         }
4392 
4393         setNewZoomScale(scale, false);
4394 
4395         if (oldScale != mActualScale) {
4396             // use mZoomPickerScale to see zoom preview first
4397             mZoomStart = SystemClock.uptimeMillis();
4398             mInvInitialZoomScale = 1.0f / oldScale;
4399             mInvFinalZoomScale = 1.0f / mActualScale;
4400             mZoomScale = mActualScale;
4401             if (!mInZoomOverview) {
4402                 mLastScale = scale;
4403             }
4404             invalidate();
4405             return true;
4406         } else {
4407             return false;
4408         }
4409     }
4410 
4411     /**
4412      * Returns a view containing zoom controls i.e. +/- buttons. The caller is
4413      * in charge of installing this view to the view hierarchy. This view will
4414      * become visible when the user starts scrolling via touch and fade away if
4415      * the user does not interact with it.
4416      * <p/>
4417      * API version 3 introduces a built-in zoom mechanism that is shown
4418      * automatically by the MapView. This is the preferred approach for
4419      * showing the zoom UI.
4420      *
4421      * @deprecated The built-in zoom mechanism is preferred, see
4422      *             {@link WebSettings#setBuiltInZoomControls(boolean)}.
4423      */
4424     @Deprecated
getZoomControls()4425     public View getZoomControls() {
4426         if (!getSettings().supportZoom()) {
4427             Log.w(LOGTAG, "This WebView doesn't support zoom.");
4428             return null;
4429         }
4430         if (mZoomControls == null) {
4431             mZoomControls = createZoomControls();
4432 
4433             /*
4434              * need to be set to VISIBLE first so that getMeasuredHeight() in
4435              * {@link #onSizeChanged()} can return the measured value for proper
4436              * layout.
4437              */
4438             mZoomControls.setVisibility(View.VISIBLE);
4439             mZoomControlRunnable = new Runnable() {
4440                 public void run() {
4441 
4442                     /* Don't dismiss the controls if the user has
4443                      * focus on them. Wait and check again later.
4444                      */
4445                     if (!mZoomControls.hasFocus()) {
4446                         mZoomControls.hide();
4447                     } else {
4448                         mPrivateHandler.removeCallbacks(mZoomControlRunnable);
4449                         mPrivateHandler.postDelayed(mZoomControlRunnable,
4450                                 ZOOM_CONTROLS_TIMEOUT);
4451                     }
4452                 }
4453             };
4454         }
4455         return mZoomControls;
4456     }
4457 
createZoomControls()4458     private ExtendedZoomControls createZoomControls() {
4459         ExtendedZoomControls zoomControls = new ExtendedZoomControls(mContext
4460             , null);
4461         zoomControls.setOnZoomInClickListener(new OnClickListener() {
4462             public void onClick(View v) {
4463                 // reset time out
4464                 mPrivateHandler.removeCallbacks(mZoomControlRunnable);
4465                 mPrivateHandler.postDelayed(mZoomControlRunnable,
4466                         ZOOM_CONTROLS_TIMEOUT);
4467                 zoomIn();
4468             }
4469         });
4470         zoomControls.setOnZoomOutClickListener(new OnClickListener() {
4471             public void onClick(View v) {
4472                 // reset time out
4473                 mPrivateHandler.removeCallbacks(mZoomControlRunnable);
4474                 mPrivateHandler.postDelayed(mZoomControlRunnable,
4475                         ZOOM_CONTROLS_TIMEOUT);
4476                 zoomOut();
4477             }
4478         });
4479         return zoomControls;
4480     }
4481 
4482     /**
4483      * Gets the {@link ZoomButtonsController} which can be used to add
4484      * additional buttons to the zoom controls window.
4485      *
4486      * @return The instance of {@link ZoomButtonsController} used by this class,
4487      *         or null if it is unavailable.
4488      * @hide
4489      */
getZoomButtonsController()4490     public ZoomButtonsController getZoomButtonsController() {
4491         return mZoomButtonsController;
4492     }
4493 
4494     /**
4495      * Perform zoom in in the webview
4496      * @return TRUE if zoom in succeeds. FALSE if no zoom changes.
4497      */
zoomIn()4498     public boolean zoomIn() {
4499         // TODO: alternatively we can disallow this during draw history mode
4500         switchOutDrawHistory();
4501         // Center zooming to the center of the screen.
4502         if (mInZoomOverview) {
4503             // if in overview mode, bring it back to normal mode
4504             mLastTouchX = getViewWidth() * .5f;
4505             mLastTouchY = getViewHeight() * .5f;
4506             doDoubleTap();
4507             return true;
4508         } else {
4509             mZoomCenterX = getViewWidth() * .5f;
4510             mZoomCenterY = getViewHeight() * .5f;
4511             return zoomWithPreview(mActualScale * 1.25f);
4512         }
4513     }
4514 
4515     /**
4516      * Perform zoom out in the webview
4517      * @return TRUE if zoom out succeeds. FALSE if no zoom changes.
4518      */
zoomOut()4519     public boolean zoomOut() {
4520         // TODO: alternatively we can disallow this during draw history mode
4521         switchOutDrawHistory();
4522         float scale = mActualScale * 0.8f;
4523         if (scale < (mMinZoomScale + 0.1f)
4524                 && mWebViewCore.getSettings().getUseWideViewPort()) {
4525             // when zoom out to min scale, switch to overview mode
4526             doDoubleTap();
4527             return true;
4528         } else {
4529             // Center zooming to the center of the screen.
4530             mZoomCenterX = getViewWidth() * .5f;
4531             mZoomCenterY = getViewHeight() * .5f;
4532             return zoomWithPreview(scale);
4533         }
4534     }
4535 
updateSelection()4536     private void updateSelection() {
4537         if (mNativeClass == 0) {
4538             return;
4539         }
4540         // mLastTouchX and mLastTouchY are the point in the current viewport
4541         int contentX = viewToContentX((int) mLastTouchX + mScrollX);
4542         int contentY = viewToContentY((int) mLastTouchY + mScrollY);
4543         Rect rect = new Rect(contentX - mNavSlop, contentY - mNavSlop,
4544                 contentX + mNavSlop, contentY + mNavSlop);
4545         nativeSelectBestAt(rect);
4546     }
4547 
4548     /**
4549      * Scroll the focused text field/area to match the WebTextView
4550      * @param xPercent New x position of the WebTextView from 0 to 1.
4551      * @param y New y position of the WebTextView in view coordinates
4552      */
scrollFocusedTextInput(float xPercent, int y)4553     /*package*/ void scrollFocusedTextInput(float xPercent, int y) {
4554         if (!inEditingMode() || mWebViewCore == null) {
4555             return;
4556         }
4557         mWebViewCore.sendMessage(EventHub.SCROLL_TEXT_INPUT,
4558                 // Since this position is relative to the top of the text input
4559                 // field, we do not need to take the title bar's height into
4560                 // consideration.
4561                 viewToContentDimension(y),
4562                 new Float(xPercent));
4563     }
4564 
4565     /**
4566      * Set our starting point and time for a drag from the WebTextView.
4567      */
initiateTextFieldDrag(float x, float y, long eventTime)4568     /*package*/ void initiateTextFieldDrag(float x, float y, long eventTime) {
4569         if (!inEditingMode()) {
4570             return;
4571         }
4572         mLastTouchX = x + (float) (mWebTextView.getLeft() - mScrollX);
4573         mLastTouchY = y + (float) (mWebTextView.getTop() - mScrollY);
4574         mLastTouchTime = eventTime;
4575         if (!mScroller.isFinished()) {
4576             abortAnimation();
4577             mPrivateHandler.removeMessages(RESUME_WEBCORE_UPDATE);
4578         }
4579         mSnapScrollMode = SNAP_NONE;
4580         mVelocityTracker = VelocityTracker.obtain();
4581         mTouchMode = TOUCH_DRAG_START_MODE;
4582     }
4583 
4584     /**
4585      * Given a motion event from the WebTextView, set its location to our
4586      * coordinates, and handle the event.
4587      */
textFieldDrag(MotionEvent event)4588     /*package*/ boolean textFieldDrag(MotionEvent event) {
4589         if (!inEditingMode()) {
4590             return false;
4591         }
4592         mDragFromTextInput = true;
4593         event.offsetLocation((float) (mWebTextView.getLeft() - mScrollX),
4594                 (float) (mWebTextView.getTop() - mScrollY));
4595         boolean result = onTouchEvent(event);
4596         mDragFromTextInput = false;
4597         return result;
4598     }
4599 
4600     /**
4601      * Do a touch up from a WebTextView.  This will be handled by webkit to
4602      * change the selection.
4603      * @param event MotionEvent in the WebTextView's coordinates.
4604      */
touchUpOnTextField(MotionEvent event)4605     /*package*/ void touchUpOnTextField(MotionEvent event) {
4606         if (!inEditingMode()) {
4607             return;
4608         }
4609         int x = viewToContentX((int) event.getX() + mWebTextView.getLeft());
4610         int y = viewToContentY((int) event.getY() + mWebTextView.getTop());
4611         // In case the soft keyboard has been dismissed, bring it back up.
4612         InputMethodManager.getInstance(getContext()).showSoftInput(mWebTextView,
4613                 0);
4614         if (nativeFocusNodePointer() != nativeCursorNodePointer()) {
4615             nativeMotionUp(x, y, mNavSlop);
4616         }
4617         nativeTextInputMotionUp(x, y);
4618     }
4619 
shortPressOnTextField()4620     /*package*/ void shortPressOnTextField() {
4621         if (inEditingMode()) {
4622             View v = mWebTextView;
4623             int x = viewToContentX((v.getLeft() + v.getRight()) >> 1);
4624             int y = viewToContentY((v.getTop() + v.getBottom()) >> 1);
4625             nativeTextInputMotionUp(x, y);
4626         }
4627     }
4628 
doShortPress()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 
doDoubleTap()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 }
5681