WebView.java revision e0a799a2ac1ca78e30fbac9e4e12a063425c08d3
1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.webkit;
18
19import android.annotation.Widget;
20import android.app.AlertDialog;
21import android.content.BroadcastReceiver;
22import android.content.ClipboardManager;
23import android.content.Context;
24import android.content.DialogInterface;
25import android.content.DialogInterface.OnCancelListener;
26import android.content.Intent;
27import android.content.IntentFilter;
28import android.content.pm.PackageInfo;
29import android.content.pm.PackageManager;
30import android.content.res.AssetManager;
31import android.content.res.Configuration;
32import android.database.DataSetObserver;
33import android.graphics.Bitmap;
34import android.graphics.BitmapFactory;
35import android.graphics.BitmapShader;
36import android.graphics.Canvas;
37import android.graphics.Color;
38import android.graphics.CornerPathEffect;
39import android.graphics.DrawFilter;
40import android.graphics.Paint;
41import android.graphics.PaintFlagsDrawFilter;
42import android.graphics.Picture;
43import android.graphics.Point;
44import android.graphics.Rect;
45import android.graphics.RectF;
46import android.graphics.Region;
47import android.graphics.SurfaceTexture;
48import android.graphics.Shader;
49import android.graphics.drawable.Drawable;
50import android.net.Proxy;
51import android.net.ProxyProperties;
52import android.net.Uri;
53import android.net.http.SslCertificate;
54import android.os.AsyncTask;
55import android.os.Bundle;
56import android.os.Handler;
57import android.os.Message;
58import android.provider.Settings;
59import android.speech.tts.TextToSpeech;
60import android.text.Selection;
61import android.text.Spannable;
62import android.util.AttributeSet;
63import android.util.EventLog;
64import android.util.Log;
65import android.view.Gravity;
66import android.view.HardwareCanvas;
67import android.view.InputDevice;
68import android.view.KeyCharacterMap;
69import android.view.KeyEvent;
70import android.view.LayoutInflater;
71import android.view.MotionEvent;
72import android.view.ScaleGestureDetector;
73import android.view.SoundEffectConstants;
74import android.view.VelocityTracker;
75import android.view.View;
76import android.view.ViewConfiguration;
77import android.view.ViewGroup;
78import android.view.ViewParent;
79import android.view.ViewTreeObserver;
80import android.view.accessibility.AccessibilityManager;
81import android.view.inputmethod.EditorInfo;
82import android.view.inputmethod.InputConnection;
83import android.view.inputmethod.InputMethodManager;
84import android.webkit.WebTextView.AutoCompleteAdapter;
85import android.webkit.WebViewCore.EventHub;
86import android.webkit.WebViewCore.TouchEventData;
87import android.webkit.WebViewCore.TouchHighlightData;
88import android.widget.AbsoluteLayout;
89import android.widget.Adapter;
90import android.widget.AdapterView;
91import android.widget.AdapterView.OnItemClickListener;
92import android.widget.ArrayAdapter;
93import android.widget.CheckedTextView;
94import android.widget.LinearLayout;
95import android.widget.ListView;
96import android.widget.OverScroller;
97import android.widget.Toast;
98
99import java.io.File;
100import java.io.FileInputStream;
101import java.io.FileNotFoundException;
102import java.io.FileOutputStream;
103import java.net.URLDecoder;
104import java.util.ArrayList;
105import java.util.HashMap;
106import java.util.HashSet;
107import java.util.List;
108import java.util.Map;
109import java.util.Set;
110import java.util.Vector;
111import java.util.regex.Matcher;
112import java.util.regex.Pattern;
113
114import junit.framework.Assert;
115
116/**
117 * <p>A View that displays web pages. This class is the basis upon which you
118 * can roll your own web browser or simply display some online content within your Activity.
119 * It uses the WebKit rendering engine to display
120 * web pages and includes methods to navigate forward and backward
121 * through a history, zoom in and out, perform text searches and more.</p>
122 * <p>To enable the built-in zoom, set
123 * {@link #getSettings() WebSettings}.{@link WebSettings#setBuiltInZoomControls(boolean)}
124 * (introduced in API version 3).
125 * <p>Note that, in order for your Activity to access the Internet and load web pages
126 * in a WebView, you must add the {@code INTERNET} permissions to your
127 * Android Manifest file:</p>
128 * <pre>&lt;uses-permission android:name="android.permission.INTERNET" /></pre>
129 *
130 * <p>This must be a child of the <a
131 * href="{@docRoot}guide/topics/manifest/manifest-element.html">{@code <manifest>}</a>
132 * element.</p>
133 *
134 * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-webview.html">Web View
135 * tutorial</a>.</p>
136 *
137 * <h3>Basic usage</h3>
138 *
139 * <p>By default, a WebView provides no browser-like widgets, does not
140 * enable JavaScript and web page errors are ignored. If your goal is only
141 * to display some HTML as a part of your UI, this is probably fine;
142 * the user won't need to interact with the web page beyond reading
143 * it, and the web page won't need to interact with the user. If you
144 * actually want a full-blown web browser, then you probably want to
145 * invoke the Browser application with a URL Intent rather than show it
146 * with a WebView. For example:
147 * <pre>
148 * Uri uri = Uri.parse("http://www.example.com");
149 * Intent intent = new Intent(Intent.ACTION_VIEW, uri);
150 * startActivity(intent);
151 * </pre>
152 * <p>See {@link android.content.Intent} for more information.</p>
153 *
154 * <p>To provide a WebView in your own Activity, include a {@code <WebView>} in your layout,
155 * or set the entire Activity window as a WebView during {@link
156 * android.app.Activity#onCreate(Bundle) onCreate()}:</p>
157 * <pre class="prettyprint">
158 * WebView webview = new WebView(this);
159 * setContentView(webview);
160 * </pre>
161 *
162 * <p>Then load the desired web page:</p>
163 * <pre>
164 * // Simplest usage: note that an exception will NOT be thrown
165 * // if there is an error loading this page (see below).
166 * webview.loadUrl("http://slashdot.org/");
167 *
168 * // OR, you can also load from an HTML string:
169 * String summary = "&lt;html>&lt;body>You scored &lt;b>192&lt;/b> points.&lt;/body>&lt;/html>";
170 * webview.loadData(summary, "text/html", "utf-8");
171 * // ... although note that there are restrictions on what this HTML can do.
172 * // See the JavaDocs for {@link #loadData(String,String,String) loadData()} and {@link
173 * #loadDataWithBaseURL(String,String,String,String,String) loadDataWithBaseURL()} for more info.
174 * </pre>
175 *
176 * <p>A WebView has several customization points where you can add your
177 * own behavior. These are:</p>
178 *
179 * <ul>
180 *   <li>Creating and setting a {@link android.webkit.WebChromeClient} subclass.
181 *       This class is called when something that might impact a
182 *       browser UI happens, for instance, progress updates and
183 *       JavaScript alerts are sent here (see <a
184 * href="{@docRoot}guide/developing/debug-tasks.html#DebuggingWebPages">Debugging Tasks</a>).
185 *   </li>
186 *   <li>Creating and setting a {@link android.webkit.WebViewClient} subclass.
187 *       It will be called when things happen that impact the
188 *       rendering of the content, eg, errors or form submissions. You
189 *       can also intercept URL loading here (via {@link
190 * android.webkit.WebViewClient#shouldOverrideUrlLoading(WebView,String)
191 * shouldOverrideUrlLoading()}).</li>
192 *   <li>Modifying the {@link android.webkit.WebSettings}, such as
193 * enabling JavaScript with {@link android.webkit.WebSettings#setJavaScriptEnabled(boolean)
194 * setJavaScriptEnabled()}. </li>
195 *   <li>Adding JavaScript-to-Java interfaces with the {@link
196 * android.webkit.WebView#addJavascriptInterface} method.
197 *       This lets you bind Java objects into the WebView so they can be
198 *       controlled from the web pages JavaScript.</li>
199 * </ul>
200 *
201 * <p>Here's a more complicated example, showing error handling,
202 *    settings, and progress notification:</p>
203 *
204 * <pre class="prettyprint">
205 * // Let's display the progress in the activity title bar, like the
206 * // browser app does.
207 * getWindow().requestFeature(Window.FEATURE_PROGRESS);
208 *
209 * webview.getSettings().setJavaScriptEnabled(true);
210 *
211 * final Activity activity = this;
212 * webview.setWebChromeClient(new WebChromeClient() {
213 *   public void onProgressChanged(WebView view, int progress) {
214 *     // Activities and WebViews measure progress with different scales.
215 *     // The progress meter will automatically disappear when we reach 100%
216 *     activity.setProgress(progress * 1000);
217 *   }
218 * });
219 * webview.setWebViewClient(new WebViewClient() {
220 *   public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
221 *     Toast.makeText(activity, "Oh no! " + description, Toast.LENGTH_SHORT).show();
222 *   }
223 * });
224 *
225 * webview.loadUrl("http://slashdot.org/");
226 * </pre>
227 *
228 * <h3>Cookie and window management</h3>
229 *
230 * <p>For obvious security reasons, your application has its own
231 * cache, cookie store etc.&mdash;it does not share the Browser
232 * application's data. Cookies are managed on a separate thread, so
233 * operations like index building don't block the UI
234 * thread. Follow the instructions in {@link android.webkit.CookieSyncManager}
235 * if you want to use cookies in your application.
236 * </p>
237 *
238 * <p>By default, requests by the HTML to open new windows are
239 * ignored. This is true whether they be opened by JavaScript or by
240 * the target attribute on a link. You can customize your
241 * {@link WebChromeClient} to provide your own behaviour for opening multiple windows,
242 * and render them in whatever manner you want.</p>
243 *
244 * <p>The standard behavior for an Activity is to be destroyed and
245 * recreated when the device orientation or any other configuration changes. This will cause
246 * the WebView to reload the current page. If you don't want that, you
247 * can set your Activity to handle the {@code orientation} and {@code keyboardHidden}
248 * changes, and then just leave the WebView alone. It'll automatically
249 * re-orient itself as appropriate. Read <a
250 * href="{@docRoot}guide/topics/resources/runtime-changes.html">Handling Runtime Changes</a> for
251 * more information about how to handle configuration changes during runtime.</p>
252 *
253 *
254 * <h3>Building web pages to support different screen densities</h3>
255 *
256 * <p>The screen density of a device is based on the screen resolution. A screen with low density
257 * has fewer available pixels per inch, where a screen with high density
258 * has more &mdash; sometimes significantly more &mdash; pixels per inch. The density of a
259 * screen is important because, other things being equal, a UI element (such as a button) whose
260 * height and width are defined in terms of screen pixels will appear larger on the lower density
261 * screen and smaller on the higher density screen.
262 * For simplicity, Android collapses all actual screen densities into three generalized densities:
263 * high, medium, and low.</p>
264 * <p>By default, WebView scales a web page so that it is drawn at a size that matches the default
265 * appearance on a medium density screen. So, it applies 1.5x scaling on a high density screen
266 * (because its pixels are smaller) and 0.75x scaling on a low density screen (because its pixels
267 * are bigger).
268 * Starting with API Level 5 (Android 2.0), WebView supports DOM, CSS, and meta tag features to help
269 * you (as a web developer) target screens with different screen densities.</p>
270 * <p>Here's a summary of the features you can use to handle different screen densities:</p>
271 * <ul>
272 * <li>The {@code window.devicePixelRatio} DOM property. The value of this property specifies the
273 * default scaling factor used for the current device. For example, if the value of {@code
274 * window.devicePixelRatio} is "1.0", then the device is considered a medium density (mdpi) device
275 * and default scaling is not applied to the web page; if the value is "1.5", then the device is
276 * considered a high density device (hdpi) and the page content is scaled 1.5x; if the
277 * value is "0.75", then the device is considered a low density device (ldpi) and the content is
278 * scaled 0.75x. However, if you specify the {@code "target-densitydpi"} meta property
279 * (discussed below), then you can stop this default scaling behavior.</li>
280 * <li>The {@code -webkit-device-pixel-ratio} CSS media query. Use this to specify the screen
281 * densities for which this style sheet is to be used. The corresponding value should be either
282 * "0.75", "1", or "1.5", to indicate that the styles are for devices with low density, medium
283 * density, or high density screens, respectively. For example:
284 * <pre>
285 * &lt;link rel="stylesheet" media="screen and (-webkit-device-pixel-ratio:1.5)" href="hdpi.css" /&gt;</pre>
286 * <p>The {@code hdpi.css} stylesheet is only used for devices with a screen pixel ration of 1.5,
287 * which is the high density pixel ratio.</p>
288 * </li>
289 * <li>The {@code target-densitydpi} property for the {@code viewport} meta tag. You can use
290 * this to specify the target density for which the web page is designed, using the following
291 * values:
292 * <ul>
293 * <li>{@code device-dpi} - Use the device's native dpi as the target dpi. Default scaling never
294 * occurs.</li>
295 * <li>{@code high-dpi} - Use hdpi as the target dpi. Medium and low density screens scale down
296 * as appropriate.</li>
297 * <li>{@code medium-dpi} - Use mdpi as the target dpi. High density screens scale up and
298 * low density screens scale down. This is also the default behavior.</li>
299 * <li>{@code low-dpi} - Use ldpi as the target dpi. Medium and high density screens scale up
300 * as appropriate.</li>
301 * <li><em>{@code <value>}</em> - Specify a dpi value to use as the target dpi (accepted
302 * values are 70-400).</li>
303 * </ul>
304 * <p>Here's an example meta tag to specify the target density:</p>
305 * <pre>&lt;meta name="viewport" content="target-densitydpi=device-dpi" /&gt;</pre></li>
306 * </ul>
307 * <p>If you want to modify your web page for different densities, by using the {@code
308 * -webkit-device-pixel-ratio} CSS media query and/or the {@code
309 * window.devicePixelRatio} DOM property, then you should set the {@code target-densitydpi} meta
310 * property to {@code device-dpi}. This stops Android from performing scaling in your web page and
311 * allows you to make the necessary adjustments for each density via CSS and JavaScript.</p>
312 *
313 *
314 */
315@Widget
316public class WebView extends AbsoluteLayout
317        implements ViewTreeObserver.OnGlobalFocusChangeListener,
318        ViewGroup.OnHierarchyChangeListener {
319
320    private class InnerGlobalLayoutListener implements ViewTreeObserver.OnGlobalLayoutListener {
321        public void onGlobalLayout() {
322            if (isShown()) {
323                setGLRectViewport();
324            }
325        }
326    }
327
328    private class InnerScrollChangedListener implements ViewTreeObserver.OnScrollChangedListener {
329        public void onScrollChanged() {
330            if (isShown()) {
331                setGLRectViewport();
332            }
333        }
334    }
335
336    // The listener to capture global layout change event.
337    private InnerGlobalLayoutListener mGlobalLayoutListener = null;
338
339    // The listener to capture scroll event.
340    private InnerScrollChangedListener mScrollChangedListener = null;
341
342    // if AUTO_REDRAW_HACK is true, then the CALL key will toggle redrawing
343    // the screen all-the-time. Good for profiling our drawing code
344    static private final boolean AUTO_REDRAW_HACK = false;
345    // true means redraw the screen all-the-time. Only with AUTO_REDRAW_HACK
346    private boolean mAutoRedraw;
347
348    // Reference to the AlertDialog displayed by InvokeListBox.
349    // It's used to dismiss the dialog in destroy if not done before.
350    private AlertDialog mListBoxDialog = null;
351
352    static final String LOGTAG = "webview";
353
354    private ZoomManager mZoomManager;
355
356    private final Rect mGLRectViewport = new Rect();
357    private final Rect mViewRectViewport = new Rect();
358    private boolean mGLViewportEmpty = false;
359
360    /**
361     *  Transportation object for returning WebView across thread boundaries.
362     */
363    public class WebViewTransport {
364        private WebView mWebview;
365
366        /**
367         * Set the WebView to the transportation object.
368         * @param webview The WebView to transport.
369         */
370        public synchronized void setWebView(WebView webview) {
371            mWebview = webview;
372        }
373
374        /**
375         * Return the WebView object.
376         * @return WebView The transported WebView object.
377         */
378        public synchronized WebView getWebView() {
379            return mWebview;
380        }
381    }
382
383    // A final CallbackProxy shared by WebViewCore and BrowserFrame.
384    private final CallbackProxy mCallbackProxy;
385
386    private final WebViewDatabase mDatabase;
387
388    // SSL certificate for the main top-level page (if secure)
389    private SslCertificate mCertificate;
390
391    // Native WebView pointer that is 0 until the native object has been
392    // created.
393    private int mNativeClass;
394    // This would be final but it needs to be set to null when the WebView is
395    // destroyed.
396    private WebViewCore mWebViewCore;
397    // Handler for dispatching UI messages.
398    /* package */ final Handler mPrivateHandler = new PrivateHandler();
399    private WebTextView mWebTextView;
400    // Used to ignore changes to webkit text that arrives to the UI side after
401    // more key events.
402    private int mTextGeneration;
403
404    /* package */ void incrementTextGeneration() { mTextGeneration++; }
405
406    // Used by WebViewCore to create child views.
407    /* package */ final ViewManager mViewManager;
408
409    // Used to display in full screen mode
410    PluginFullScreenHolder mFullScreenHolder;
411
412    /**
413     * Position of the last touch event in pixels.
414     * Use integer to prevent loss of dragging delta calculation accuracy;
415     * which was done in float and converted to integer, and resulted in gradual
416     * and compounding touch position and view dragging mismatch.
417     */
418    private int mLastTouchX;
419    private int mLastTouchY;
420
421    /**
422     * Time of the last touch event.
423     */
424    private long mLastTouchTime;
425
426    /**
427     * Time of the last time sending touch event to WebViewCore
428     */
429    private long mLastSentTouchTime;
430
431    /**
432     * The minimum elapsed time before sending another ACTION_MOVE event to
433     * WebViewCore. This really should be tuned for each type of the devices.
434     * For example in Google Map api test case, it takes Dream device at least
435     * 150ms to do a full cycle in the WebViewCore by processing a touch event,
436     * triggering the layout and drawing the picture. While the same process
437     * takes 60+ms on the current high speed device. If we make
438     * TOUCH_SENT_INTERVAL too small, there will be multiple touch events sent
439     * to WebViewCore queue and the real layout and draw events will be pushed
440     * to further, which slows down the refresh rate. Choose 50 to favor the
441     * current high speed devices. For Dream like devices, 100 is a better
442     * choice. Maybe make this in the buildspec later.
443     * (Update 12/14/2010: changed to 0 since current device should be able to
444     * handle the raw events and Map team voted to have the raw events too.
445     */
446    private static final int TOUCH_SENT_INTERVAL = 0;
447    private int mCurrentTouchInterval = TOUCH_SENT_INTERVAL;
448
449    /**
450     * Helper class to get velocity for fling
451     */
452    VelocityTracker mVelocityTracker;
453    private int mMaximumFling;
454    private float mLastVelocity;
455    private float mLastVelX;
456    private float mLastVelY;
457
458    // The id of the native layer being scrolled.
459    private int mScrollingLayer;
460    private Rect mScrollingLayerRect = new Rect();
461
462    // only trigger accelerated fling if the new velocity is at least
463    // MINIMUM_VELOCITY_RATIO_FOR_ACCELERATION times of the previous velocity
464    private static final float MINIMUM_VELOCITY_RATIO_FOR_ACCELERATION = 0.2f;
465
466    /**
467     * Touch mode
468     */
469    private int mTouchMode = TOUCH_DONE_MODE;
470    private static final int TOUCH_INIT_MODE = 1;
471    private static final int TOUCH_DRAG_START_MODE = 2;
472    private static final int TOUCH_DRAG_MODE = 3;
473    private static final int TOUCH_SHORTPRESS_START_MODE = 4;
474    private static final int TOUCH_SHORTPRESS_MODE = 5;
475    private static final int TOUCH_DOUBLE_TAP_MODE = 6;
476    private static final int TOUCH_DONE_MODE = 7;
477    private static final int TOUCH_PINCH_DRAG = 8;
478    private static final int TOUCH_DRAG_LAYER_MODE = 9;
479
480    // Whether to forward the touch events to WebCore
481    // Can only be set by WebKit via JNI.
482    private boolean mForwardTouchEvents = false;
483
484    // Whether to prevent default during touch. The initial value depends on
485    // mForwardTouchEvents. If WebCore wants all the touch events, it says yes
486    // for touch down. Otherwise UI will wait for the answer of the first
487    // confirmed move before taking over the control.
488    private static final int PREVENT_DEFAULT_NO = 0;
489    private static final int PREVENT_DEFAULT_MAYBE_YES = 1;
490    private static final int PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN = 2;
491    private static final int PREVENT_DEFAULT_YES = 3;
492    private static final int PREVENT_DEFAULT_IGNORE = 4;
493    private int mPreventDefault = PREVENT_DEFAULT_IGNORE;
494
495    // true when the touch movement exceeds the slop
496    private boolean mConfirmMove;
497
498    // if true, touch events will be first processed by WebCore, if prevent
499    // default is not set, the UI will continue handle them.
500    private boolean mDeferTouchProcess;
501
502    // to avoid interfering with the current touch events, track them
503    // separately. Currently no snapping or fling in the deferred process mode
504    private int mDeferTouchMode = TOUCH_DONE_MODE;
505    private float mLastDeferTouchX;
506    private float mLastDeferTouchY;
507
508    // To keep track of whether the current drag was initiated by a WebTextView,
509    // so that we know not to hide the cursor
510    boolean mDragFromTextInput;
511
512    // Whether or not to draw the cursor ring.
513    private boolean mDrawCursorRing = true;
514
515    // true if onPause has been called (and not onResume)
516    private boolean mIsPaused;
517
518    private HitTestResult mInitialHitTestResult;
519
520    /**
521     * Customizable constant
522     */
523    // pre-computed square of ViewConfiguration.getScaledTouchSlop()
524    private int mTouchSlopSquare;
525    // pre-computed square of ViewConfiguration.getScaledDoubleTapSlop()
526    private int mDoubleTapSlopSquare;
527    // pre-computed density adjusted navigation slop
528    private int mNavSlop;
529    // This should be ViewConfiguration.getTapTimeout()
530    // But system time out is 100ms, which is too short for the browser.
531    // In the browser, if it switches out of tap too soon, jump tap won't work.
532    private static final int TAP_TIMEOUT = 200;
533    // This should be ViewConfiguration.getLongPressTimeout()
534    // But system time out is 500ms, which is too short for the browser.
535    // With a short timeout, it's difficult to treat trigger a short press.
536    private static final int LONG_PRESS_TIMEOUT = 1000;
537    // needed to avoid flinging after a pause of no movement
538    private static final int MIN_FLING_TIME = 250;
539    // draw unfiltered after drag is held without movement
540    private static final int MOTIONLESS_TIME = 100;
541    // The amount of content to overlap between two screens when going through
542    // pages with the space bar, in pixels.
543    private static final int PAGE_SCROLL_OVERLAP = 24;
544
545    /**
546     * These prevent calling requestLayout if either dimension is fixed. This
547     * depends on the layout parameters and the measure specs.
548     */
549    boolean mWidthCanMeasure;
550    boolean mHeightCanMeasure;
551
552    // Remember the last dimensions we sent to the native side so we can avoid
553    // sending the same dimensions more than once.
554    int mLastWidthSent;
555    int mLastHeightSent;
556    // Since view height sent to webkit could be fixed to avoid relayout, this
557    // value records the last sent actual view height.
558    int mLastActualHeightSent;
559
560    private int mContentWidth;   // cache of value from WebViewCore
561    private int mContentHeight;  // cache of value from WebViewCore
562
563    // Need to have the separate control for horizontal and vertical scrollbar
564    // style than the View's single scrollbar style
565    private boolean mOverlayHorizontalScrollbar = true;
566    private boolean mOverlayVerticalScrollbar = false;
567
568    // our standard speed. this way small distances will be traversed in less
569    // time than large distances, but we cap the duration, so that very large
570    // distances won't take too long to get there.
571    private static final int STD_SPEED = 480;  // pixels per second
572    // time for the longest scroll animation
573    private static final int MAX_DURATION = 750;   // milliseconds
574    private static final int SLIDE_TITLE_DURATION = 500;   // milliseconds
575
576    // Used by OverScrollGlow
577    OverScroller mScroller;
578
579    private boolean mInOverScrollMode = false;
580    private static Paint mOverScrollBackground;
581    private static Paint mOverScrollBorder;
582
583    private boolean mWrapContent;
584    private static final int MOTIONLESS_FALSE           = 0;
585    private static final int MOTIONLESS_PENDING         = 1;
586    private static final int MOTIONLESS_TRUE            = 2;
587    private static final int MOTIONLESS_IGNORE          = 3;
588    private int mHeldMotionless;
589
590    // An instance for injecting accessibility in WebViews with disabled
591    // JavaScript or ones for which no accessibility script exists
592    private AccessibilityInjector mAccessibilityInjector;
593
594    // flag indicating if accessibility script is injected so we
595    // know to handle Shift and arrows natively first
596    private boolean mAccessibilityScriptInjected;
597
598    // the color used to highlight the touch rectangles
599    private static final int mHightlightColor = 0x33000000;
600    // the round corner for the highlight path
601    private static final float TOUCH_HIGHLIGHT_ARC = 5.0f;
602    // the region indicating where the user touched on the screen
603    private Region mTouchHighlightRegion = new Region();
604    // the paint for the touch highlight
605    private Paint mTouchHightlightPaint;
606    // debug only
607    private static final boolean DEBUG_TOUCH_HIGHLIGHT = true;
608    private static final int TOUCH_HIGHLIGHT_ELAPSE_TIME = 2000;
609    private Paint mTouchCrossHairColor;
610    private int mTouchHighlightX;
611    private int mTouchHighlightY;
612
613    // Basically this proxy is used to tell the Video to update layer tree at
614    // SetBaseLayer time and to pause when WebView paused.
615    private HTML5VideoViewProxy mHTML5VideoViewProxy;
616
617    /*
618     * Private message ids
619     */
620    private static final int REMEMBER_PASSWORD          = 1;
621    private static final int NEVER_REMEMBER_PASSWORD    = 2;
622    private static final int SWITCH_TO_SHORTPRESS       = 3;
623    private static final int SWITCH_TO_LONGPRESS        = 4;
624    private static final int RELEASE_SINGLE_TAP         = 5;
625    private static final int REQUEST_FORM_DATA          = 6;
626    private static final int RESUME_WEBCORE_PRIORITY    = 7;
627    private static final int DRAG_HELD_MOTIONLESS       = 8;
628    private static final int AWAKEN_SCROLL_BARS         = 9;
629    private static final int PREVENT_DEFAULT_TIMEOUT    = 10;
630    private static final int SCROLL_SELECT_TEXT         = 11;
631
632
633    private static final int FIRST_PRIVATE_MSG_ID = REMEMBER_PASSWORD;
634    private static final int LAST_PRIVATE_MSG_ID = SCROLL_SELECT_TEXT;
635
636    /*
637     * Package message ids
638     */
639    static final int SCROLL_TO_MSG_ID                   = 101;
640    static final int NEW_PICTURE_MSG_ID                 = 105;
641    static final int UPDATE_TEXT_ENTRY_MSG_ID           = 106;
642    static final int WEBCORE_INITIALIZED_MSG_ID         = 107;
643    static final int UPDATE_TEXTFIELD_TEXT_MSG_ID       = 108;
644    static final int UPDATE_ZOOM_RANGE                  = 109;
645    static final int UNHANDLED_NAV_KEY                  = 110;
646    static final int CLEAR_TEXT_ENTRY                   = 111;
647    static final int UPDATE_TEXT_SELECTION_MSG_ID       = 112;
648    static final int SHOW_RECT_MSG_ID                   = 113;
649    static final int LONG_PRESS_CENTER                  = 114;
650    static final int PREVENT_TOUCH_ID                   = 115;
651    static final int WEBCORE_NEED_TOUCH_EVENTS          = 116;
652    // obj=Rect in doc coordinates
653    static final int INVAL_RECT_MSG_ID                  = 117;
654    static final int REQUEST_KEYBOARD                   = 118;
655    static final int DO_MOTION_UP                       = 119;
656    static final int SHOW_FULLSCREEN                    = 120;
657    static final int HIDE_FULLSCREEN                    = 121;
658    static final int DOM_FOCUS_CHANGED                  = 122;
659    static final int REPLACE_BASE_CONTENT               = 123;
660    static final int FORM_DID_BLUR                      = 124;
661    static final int RETURN_LABEL                       = 125;
662    static final int FIND_AGAIN                         = 126;
663    static final int CENTER_FIT_RECT                    = 127;
664    static final int REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID = 128;
665    static final int SET_SCROLLBAR_MODES                = 129;
666    static final int SELECTION_STRING_CHANGED           = 130;
667    static final int SET_TOUCH_HIGHLIGHT_RECTS          = 131;
668    static final int SAVE_WEBARCHIVE_FINISHED           = 132;
669
670    static final int SET_AUTOFILLABLE                   = 133;
671    static final int AUTOFILL_COMPLETE                  = 134;
672
673    static final int SELECT_AT                          = 135;
674    static final int SCREEN_ON                          = 136;
675    static final int ENTER_FULLSCREEN_VIDEO             = 137;
676
677    private static final int FIRST_PACKAGE_MSG_ID = SCROLL_TO_MSG_ID;
678    private static final int LAST_PACKAGE_MSG_ID = SET_TOUCH_HIGHLIGHT_RECTS;
679
680    static final String[] HandlerPrivateDebugString = {
681        "REMEMBER_PASSWORD", //              = 1;
682        "NEVER_REMEMBER_PASSWORD", //        = 2;
683        "SWITCH_TO_SHORTPRESS", //           = 3;
684        "SWITCH_TO_LONGPRESS", //            = 4;
685        "RELEASE_SINGLE_TAP", //             = 5;
686        "REQUEST_FORM_DATA", //              = 6;
687        "RESUME_WEBCORE_PRIORITY", //        = 7;
688        "DRAG_HELD_MOTIONLESS", //           = 8;
689        "AWAKEN_SCROLL_BARS", //             = 9;
690        "PREVENT_DEFAULT_TIMEOUT", //        = 10;
691        "SCROLL_SELECT_TEXT" //              = 11;
692    };
693
694    static final String[] HandlerPackageDebugString = {
695        "SCROLL_TO_MSG_ID", //               = 101;
696        "102", //                            = 102;
697        "103", //                            = 103;
698        "104", //                            = 104;
699        "NEW_PICTURE_MSG_ID", //             = 105;
700        "UPDATE_TEXT_ENTRY_MSG_ID", //       = 106;
701        "WEBCORE_INITIALIZED_MSG_ID", //     = 107;
702        "UPDATE_TEXTFIELD_TEXT_MSG_ID", //   = 108;
703        "UPDATE_ZOOM_RANGE", //              = 109;
704        "UNHANDLED_NAV_KEY", //              = 110;
705        "CLEAR_TEXT_ENTRY", //               = 111;
706        "UPDATE_TEXT_SELECTION_MSG_ID", //   = 112;
707        "SHOW_RECT_MSG_ID", //               = 113;
708        "LONG_PRESS_CENTER", //              = 114;
709        "PREVENT_TOUCH_ID", //               = 115;
710        "WEBCORE_NEED_TOUCH_EVENTS", //      = 116;
711        "INVAL_RECT_MSG_ID", //              = 117;
712        "REQUEST_KEYBOARD", //               = 118;
713        "DO_MOTION_UP", //                   = 119;
714        "SHOW_FULLSCREEN", //                = 120;
715        "HIDE_FULLSCREEN", //                = 121;
716        "DOM_FOCUS_CHANGED", //              = 122;
717        "REPLACE_BASE_CONTENT", //           = 123;
718        "FORM_DID_BLUR", //                  = 124;
719        "RETURN_LABEL", //                   = 125;
720        "FIND_AGAIN", //                     = 126;
721        "CENTER_FIT_RECT", //                = 127;
722        "REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID", // = 128;
723        "SET_SCROLLBAR_MODES", //            = 129;
724        "SELECTION_STRING_CHANGED", //       = 130;
725        "SET_TOUCH_HIGHLIGHT_RECTS", //      = 131;
726        "SAVE_WEBARCHIVE_FINISHED", //       = 132;
727        "SET_AUTOFILLABLE", //               = 133;
728        "AUTOFILL_COMPLETE", //              = 134;
729        "SELECT_AT", //                      = 135;
730        "SCREEN_ON", //                      = 136;
731        "ENTER_FULLSCREEN_VIDEO" //          = 137;
732    };
733
734    // If the site doesn't use the viewport meta tag to specify the viewport,
735    // use DEFAULT_VIEWPORT_WIDTH as the default viewport width
736    static final int DEFAULT_VIEWPORT_WIDTH = 980;
737
738    // normally we try to fit the content to the minimum preferred width
739    // calculated by the Webkit. To avoid the bad behavior when some site's
740    // minimum preferred width keeps growing when changing the viewport width or
741    // the minimum preferred width is huge, an upper limit is needed.
742    static int sMaxViewportWidth = DEFAULT_VIEWPORT_WIDTH;
743
744    // initial scale in percent. 0 means using default.
745    private int mInitialScaleInPercent = 0;
746
747    // Whether or not a scroll event should be sent to webkit.  This is only set
748    // to false when restoring the scroll position.
749    private boolean mSendScrollEvent = true;
750
751    private int mSnapScrollMode = SNAP_NONE;
752    private static final int SNAP_NONE = 0;
753    private static final int SNAP_LOCK = 1; // not a separate state
754    private static final int SNAP_X = 2; // may be combined with SNAP_LOCK
755    private static final int SNAP_Y = 4; // may be combined with SNAP_LOCK
756    private boolean mSnapPositive;
757
758    // keep these in sync with their counterparts in WebView.cpp
759    private static final int DRAW_EXTRAS_NONE = 0;
760    private static final int DRAW_EXTRAS_FIND = 1;
761    private static final int DRAW_EXTRAS_SELECTION = 2;
762    private static final int DRAW_EXTRAS_CURSOR_RING = 3;
763
764    // keep this in sync with WebCore:ScrollbarMode in WebKit
765    private static final int SCROLLBAR_AUTO = 0;
766    private static final int SCROLLBAR_ALWAYSOFF = 1;
767    // as we auto fade scrollbar, this is ignored.
768    private static final int SCROLLBAR_ALWAYSON = 2;
769    private int mHorizontalScrollBarMode = SCROLLBAR_AUTO;
770    private int mVerticalScrollBarMode = SCROLLBAR_AUTO;
771
772    // constants for determining script injection strategy
773    private static final int ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED = -1;
774    private static final int ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT = 0;
775    private static final int ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED = 1;
776
777    // the alias via which accessibility JavaScript interface is exposed
778    private static final String ALIAS_ACCESSIBILITY_JS_INTERFACE = "accessibility";
779
780    // JavaScript to inject the script chooser which will
781    // pick the right script for the current URL
782    private static final String ACCESSIBILITY_SCRIPT_CHOOSER_JAVASCRIPT =
783        "javascript:(function() {" +
784        "    var chooser = document.createElement('script');" +
785        "    chooser.type = 'text/javascript';" +
786        "    chooser.src = 'https://ssl.gstatic.com/accessibility/javascript/android/AndroidScriptChooser.user.js';" +
787        "    document.getElementsByTagName('head')[0].appendChild(chooser);" +
788        "  })();";
789
790    // Regular expression that matches the "axs" URL parameter.
791    // The value of 0 means the accessibility script is opted out
792    // The value of 1 means the accessibility script is already injected
793    private static final String PATTERN_MATCH_AXS_URL_PARAMETER = "(\\?axs=(0|1))|(&axs=(0|1))";
794
795    // TextToSpeech instance exposed to JavaScript to the injected screenreader.
796    private TextToSpeech mTextToSpeech;
797
798    // variable to cache the above pattern in case accessibility is enabled.
799    private Pattern mMatchAxsUrlParameterPattern;
800
801    /**
802     * Max distance to overscroll by in pixels.
803     * This how far content can be pulled beyond its normal bounds by the user.
804     */
805    private int mOverscrollDistance;
806
807    /**
808     * Max distance to overfling by in pixels.
809     * This is how far flinged content can move beyond the end of its normal bounds.
810     */
811    private int mOverflingDistance;
812
813    private OverScrollGlow mOverScrollGlow;
814
815    // Used to match key downs and key ups
816    private Vector<Integer> mKeysPressed;
817
818    /* package */ static boolean mLogEvent = true;
819
820    // for event log
821    private long mLastTouchUpTime = 0;
822
823    private WebViewCore.AutoFillData mAutoFillData;
824
825    /**
826     * URI scheme for telephone number
827     */
828    public static final String SCHEME_TEL = "tel:";
829    /**
830     * URI scheme for email address
831     */
832    public static final String SCHEME_MAILTO = "mailto:";
833    /**
834     * URI scheme for map address
835     */
836    public static final String SCHEME_GEO = "geo:0,0?q=";
837
838    private int mBackgroundColor = Color.WHITE;
839
840    private static final long SELECT_SCROLL_INTERVAL = 1000 / 60; // 60 / second
841    private int mAutoScrollX = 0;
842    private int mAutoScrollY = 0;
843    private int mMinAutoScrollX = 0;
844    private int mMaxAutoScrollX = 0;
845    private int mMinAutoScrollY = 0;
846    private int mMaxAutoScrollY = 0;
847    private Rect mScrollingLayerBounds = new Rect();
848    private boolean mSentAutoScrollMessage = false;
849
850    // used for serializing asynchronously handled touch events.
851    private final TouchEventQueue mTouchEventQueue = new TouchEventQueue();
852
853    // Used to notify listeners of a new picture.
854    private PictureListener mPictureListener;
855    /**
856     * Interface to listen for new pictures as they change.
857     */
858    public interface PictureListener {
859        /**
860         * Notify the listener that the picture has changed.
861         * @param view The WebView that owns the picture.
862         * @param picture The new picture.
863         */
864        public void onNewPicture(WebView view, Picture picture);
865    }
866
867    // FIXME: Want to make this public, but need to change the API file.
868    public /*static*/ class HitTestResult {
869        /**
870         * Default HitTestResult, where the target is unknown
871         */
872        public static final int UNKNOWN_TYPE = 0;
873        /**
874         * @deprecated This type is no longer used.
875         */
876        @Deprecated
877        public static final int ANCHOR_TYPE = 1;
878        /**
879         * HitTestResult for hitting a phone number
880         */
881        public static final int PHONE_TYPE = 2;
882        /**
883         * HitTestResult for hitting a map address
884         */
885        public static final int GEO_TYPE = 3;
886        /**
887         * HitTestResult for hitting an email address
888         */
889        public static final int EMAIL_TYPE = 4;
890        /**
891         * HitTestResult for hitting an HTML::img tag
892         */
893        public static final int IMAGE_TYPE = 5;
894        /**
895         * @deprecated This type is no longer used.
896         */
897        @Deprecated
898        public static final int IMAGE_ANCHOR_TYPE = 6;
899        /**
900         * HitTestResult for hitting a HTML::a tag with src=http
901         */
902        public static final int SRC_ANCHOR_TYPE = 7;
903        /**
904         * HitTestResult for hitting a HTML::a tag with src=http + HTML::img
905         */
906        public static final int SRC_IMAGE_ANCHOR_TYPE = 8;
907        /**
908         * HitTestResult for hitting an edit text area
909         */
910        public static final int EDIT_TEXT_TYPE = 9;
911
912        private int mType;
913        private String mExtra;
914
915        HitTestResult() {
916            mType = UNKNOWN_TYPE;
917        }
918
919        private void setType(int type) {
920            mType = type;
921        }
922
923        private void setExtra(String extra) {
924            mExtra = extra;
925        }
926
927        public int getType() {
928            return mType;
929        }
930
931        public String getExtra() {
932            return mExtra;
933        }
934    }
935
936    /**
937     * Construct a new WebView with a Context object.
938     * @param context A Context object used to access application assets.
939     */
940    public WebView(Context context) {
941        this(context, null);
942    }
943
944    /**
945     * Construct a new WebView with layout parameters.
946     * @param context A Context object used to access application assets.
947     * @param attrs An AttributeSet passed to our parent.
948     */
949    public WebView(Context context, AttributeSet attrs) {
950        this(context, attrs, com.android.internal.R.attr.webViewStyle);
951    }
952
953    /**
954     * Construct a new WebView with layout parameters and a default style.
955     * @param context A Context object used to access application assets.
956     * @param attrs An AttributeSet passed to our parent.
957     * @param defStyle The default style resource ID.
958     */
959    public WebView(Context context, AttributeSet attrs, int defStyle) {
960        this(context, attrs, defStyle, false);
961    }
962
963    /**
964     * Construct a new WebView with layout parameters and a default style.
965     * @param context A Context object used to access application assets.
966     * @param attrs An AttributeSet passed to our parent.
967     * @param defStyle The default style resource ID.
968     */
969    public WebView(Context context, AttributeSet attrs, int defStyle,
970            boolean privateBrowsing) {
971        this(context, attrs, defStyle, null, privateBrowsing);
972    }
973
974    /**
975     * Construct a new WebView with layout parameters, a default style and a set
976     * of custom Javscript interfaces to be added to the WebView at initialization
977     * time. This guarantees that these interfaces will be available when the JS
978     * context is initialized.
979     * @param context A Context object used to access application assets.
980     * @param attrs An AttributeSet passed to our parent.
981     * @param defStyle The default style resource ID.
982     * @param javaScriptInterfaces is a Map of interface names, as keys, and
983     * object implementing those interfaces, as values.
984     * @hide pending API council approval.
985     */
986    protected WebView(Context context, AttributeSet attrs, int defStyle,
987            Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) {
988        super(context, attrs, defStyle);
989
990        // Used by the chrome stack to find application paths
991        JniUtil.setContext(context);
992
993        mCallbackProxy = new CallbackProxy(context, this);
994        mViewManager = new ViewManager(this);
995        L10nUtils.setApplicationContext(context.getApplicationContext());
996        mWebViewCore = new WebViewCore(context, this, mCallbackProxy, javaScriptInterfaces);
997        mDatabase = WebViewDatabase.getInstance(context);
998        mScroller = new OverScroller(context, null, 0, 0, false); //TODO Use OverScroller's flywheel
999        mZoomManager = new ZoomManager(this, mCallbackProxy);
1000
1001        /* The init method must follow the creation of certain member variables,
1002         * such as the mZoomManager.
1003         */
1004        init();
1005        setupPackageListener(context);
1006        setupProxyListener(context);
1007        updateMultiTouchSupport(context);
1008
1009        if (privateBrowsing) {
1010            startPrivateBrowsing();
1011        }
1012
1013        mAutoFillData = new WebViewCore.AutoFillData();
1014    }
1015
1016    private static class ProxyReceiver extends BroadcastReceiver {
1017        @Override
1018        public void onReceive(Context context, Intent intent) {
1019            if (intent.getAction().equals(Proxy.PROXY_CHANGE_ACTION)) {
1020                handleProxyBroadcast(intent);
1021            }
1022        }
1023    }
1024
1025    /*
1026     * A variable to track if there is a receiver added for PROXY_CHANGE_ACTION
1027     */
1028    private static boolean sProxyReceiverAdded;
1029
1030    private static synchronized void setupProxyListener(Context context) {
1031        if (sProxyReceiverAdded) {
1032            return;
1033        }
1034        IntentFilter filter = new IntentFilter();
1035        filter.addAction(Proxy.PROXY_CHANGE_ACTION);
1036        Intent currentProxy = context.getApplicationContext().registerReceiver(
1037                new ProxyReceiver(), filter);
1038        sProxyReceiverAdded = true;
1039        if (currentProxy != null) {
1040            handleProxyBroadcast(currentProxy);
1041        }
1042    }
1043
1044    private static void handleProxyBroadcast(Intent intent) {
1045        ProxyProperties proxyProperties = (ProxyProperties)intent.getExtra(Proxy.EXTRA_PROXY_INFO);
1046        if (proxyProperties == null || proxyProperties.getHost() == null) {
1047            WebViewCore.sendStaticMessage(EventHub.PROXY_CHANGED, "");
1048            return;
1049        }
1050
1051        String host = proxyProperties.getHost();
1052        int port = proxyProperties.getPort();
1053        if (port != 0)
1054            host += ":" + port;
1055
1056        // TODO: Handle exclusion list
1057        // The plan is to make an AndroidProxyResolver, and handle the blacklist
1058        // there
1059        String exclusionList = proxyProperties.getExclusionList();
1060        WebViewCore.sendStaticMessage(EventHub.PROXY_CHANGED, host);
1061    }
1062
1063    /*
1064     * A variable to track if there is a receiver added for ACTION_PACKAGE_ADDED
1065     * or ACTION_PACKAGE_REMOVED.
1066     */
1067    private static boolean sPackageInstallationReceiverAdded = false;
1068
1069    /*
1070     * A set of Google packages we monitor for the
1071     * navigator.isApplicationInstalled() API. Add additional packages as
1072     * needed.
1073     */
1074    private static Set<String> sGoogleApps;
1075    static {
1076        sGoogleApps = new HashSet<String>();
1077        sGoogleApps.add("com.google.android.youtube");
1078    }
1079
1080    private static class PackageListener extends BroadcastReceiver {
1081        @Override
1082        public void onReceive(Context context, Intent intent) {
1083            final String action = intent.getAction();
1084            final String packageName = intent.getData().getSchemeSpecificPart();
1085            final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
1086            if (Intent.ACTION_PACKAGE_REMOVED.equals(action) && replacing) {
1087                // if it is replacing, refreshPlugins() when adding
1088                return;
1089            }
1090
1091            if (sGoogleApps.contains(packageName)) {
1092                if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
1093                    WebViewCore.sendStaticMessage(EventHub.ADD_PACKAGE_NAME, packageName);
1094                } else {
1095                    WebViewCore.sendStaticMessage(EventHub.REMOVE_PACKAGE_NAME, packageName);
1096                }
1097            }
1098
1099            PluginManager pm = PluginManager.getInstance(context);
1100            if (pm.containsPluginPermissionAndSignatures(packageName)) {
1101                pm.refreshPlugins(Intent.ACTION_PACKAGE_ADDED.equals(action));
1102            }
1103        }
1104    }
1105
1106    private void setupPackageListener(Context context) {
1107
1108        /*
1109         * we must synchronize the instance check and the creation of the
1110         * receiver to ensure that only ONE receiver exists for all WebView
1111         * instances.
1112         */
1113        synchronized (WebView.class) {
1114
1115            // if the receiver already exists then we do not need to register it
1116            // again
1117            if (sPackageInstallationReceiverAdded) {
1118                return;
1119            }
1120
1121            IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
1122            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
1123            filter.addDataScheme("package");
1124            BroadcastReceiver packageListener = new PackageListener();
1125            context.getApplicationContext().registerReceiver(packageListener, filter);
1126            sPackageInstallationReceiverAdded = true;
1127        }
1128
1129        // check if any of the monitored apps are already installed
1130        AsyncTask<Void, Void, Set<String>> task = new AsyncTask<Void, Void, Set<String>>() {
1131
1132            @Override
1133            protected Set<String> doInBackground(Void... unused) {
1134                Set<String> installedPackages = new HashSet<String>();
1135                PackageManager pm = mContext.getPackageManager();
1136                for (String name : sGoogleApps) {
1137                    try {
1138                        PackageInfo pInfo = pm.getPackageInfo(name,
1139                                PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES);
1140                        installedPackages.add(name);
1141                    } catch(PackageManager.NameNotFoundException e) {
1142                        // package not found
1143                    }
1144                }
1145                return installedPackages;
1146            }
1147
1148            // Executes on the UI thread
1149            @Override
1150            protected void onPostExecute(Set<String> installedPackages) {
1151                if (mWebViewCore != null) {
1152                    mWebViewCore.sendMessage(EventHub.ADD_PACKAGE_NAMES, installedPackages);
1153                }
1154            }
1155        };
1156        task.execute();
1157    }
1158
1159    void updateMultiTouchSupport(Context context) {
1160        mZoomManager.updateMultiTouchSupport(context);
1161    }
1162
1163    private void init() {
1164        setWillNotDraw(false);
1165        setFocusable(true);
1166        setFocusableInTouchMode(true);
1167        setClickable(true);
1168        setLongClickable(true);
1169
1170        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
1171        int slop = configuration.getScaledTouchSlop();
1172        mTouchSlopSquare = slop * slop;
1173        mMinLockSnapReverseDistance = slop;
1174        slop = configuration.getScaledDoubleTapSlop();
1175        mDoubleTapSlopSquare = slop * slop;
1176        final float density = getContext().getResources().getDisplayMetrics().density;
1177        // use one line height, 16 based on our current default font, for how
1178        // far we allow a touch be away from the edge of a link
1179        mNavSlop = (int) (16 * density);
1180        mZoomManager.init(density);
1181        mMaximumFling = configuration.getScaledMaximumFlingVelocity();
1182
1183        // Compute the inverse of the density squared.
1184        DRAG_LAYER_INVERSE_DENSITY_SQUARED = 1 / (density * density);
1185
1186        mOverscrollDistance = configuration.getScaledOverscrollDistance();
1187        mOverflingDistance = configuration.getScaledOverflingDistance();
1188
1189        setScrollBarStyle(super.getScrollBarStyle());
1190        // Initially use a size of two, since the user is likely to only hold
1191        // down two keys at a time (shift + another key)
1192        mKeysPressed = new Vector<Integer>(2);
1193        mHTML5VideoViewProxy = null ;
1194    }
1195
1196    @Override
1197    public boolean shouldDelayChildPressedState() {
1198        return true;
1199    }
1200
1201    /**
1202     * Adds accessibility APIs to JavaScript.
1203     *
1204     * Note: This method is responsible to performing the necessary
1205     *       check if the accessibility APIs should be exposed.
1206     */
1207    private void addAccessibilityApisToJavaScript() {
1208        if (AccessibilityManager.getInstance(mContext).isEnabled()
1209                && getSettings().getJavaScriptEnabled()) {
1210            // exposing the TTS for now ...
1211            mTextToSpeech = new TextToSpeech(getContext(), null);
1212            addJavascriptInterface(mTextToSpeech, ALIAS_ACCESSIBILITY_JS_INTERFACE);
1213        }
1214    }
1215
1216    /**
1217     * Removes accessibility APIs from JavaScript.
1218     */
1219    private void removeAccessibilityApisFromJavaScript() {
1220        // exposing the TTS for now ...
1221        if (mTextToSpeech != null) {
1222            removeJavascriptInterface(ALIAS_ACCESSIBILITY_JS_INTERFACE);
1223            mTextToSpeech.shutdown();
1224            mTextToSpeech = null;
1225        }
1226    }
1227
1228    @Override
1229    public void setOverScrollMode(int mode) {
1230        super.setOverScrollMode(mode);
1231        if (mode != OVER_SCROLL_NEVER) {
1232            if (mOverScrollGlow == null) {
1233                mOverScrollGlow = new OverScrollGlow(this);
1234            }
1235        } else {
1236            mOverScrollGlow = null;
1237        }
1238    }
1239
1240    /* package */void updateDefaultZoomDensity(int zoomDensity) {
1241        final float density = mContext.getResources().getDisplayMetrics().density
1242                * 100 / zoomDensity;
1243        mNavSlop = (int) (16 * density);
1244        mZoomManager.updateDefaultZoomDensity(density);
1245    }
1246
1247    /* package */ boolean onSavePassword(String schemePlusHost, String username,
1248            String password, final Message resumeMsg) {
1249       boolean rVal = false;
1250       if (resumeMsg == null) {
1251           // null resumeMsg implies saving password silently
1252           mDatabase.setUsernamePassword(schemePlusHost, username, password);
1253       } else {
1254            final Message remember = mPrivateHandler.obtainMessage(
1255                    REMEMBER_PASSWORD);
1256            remember.getData().putString("host", schemePlusHost);
1257            remember.getData().putString("username", username);
1258            remember.getData().putString("password", password);
1259            remember.obj = resumeMsg;
1260
1261            final Message neverRemember = mPrivateHandler.obtainMessage(
1262                    NEVER_REMEMBER_PASSWORD);
1263            neverRemember.getData().putString("host", schemePlusHost);
1264            neverRemember.getData().putString("username", username);
1265            neverRemember.getData().putString("password", password);
1266            neverRemember.obj = resumeMsg;
1267
1268            new AlertDialog.Builder(getContext())
1269                    .setTitle(com.android.internal.R.string.save_password_label)
1270                    .setMessage(com.android.internal.R.string.save_password_message)
1271                    .setPositiveButton(com.android.internal.R.string.save_password_notnow,
1272                    new DialogInterface.OnClickListener() {
1273                        public void onClick(DialogInterface dialog, int which) {
1274                            resumeMsg.sendToTarget();
1275                        }
1276                    })
1277                    .setNeutralButton(com.android.internal.R.string.save_password_remember,
1278                    new DialogInterface.OnClickListener() {
1279                        public void onClick(DialogInterface dialog, int which) {
1280                            remember.sendToTarget();
1281                        }
1282                    })
1283                    .setNegativeButton(com.android.internal.R.string.save_password_never,
1284                    new DialogInterface.OnClickListener() {
1285                        public void onClick(DialogInterface dialog, int which) {
1286                            neverRemember.sendToTarget();
1287                        }
1288                    })
1289                    .setOnCancelListener(new OnCancelListener() {
1290                        public void onCancel(DialogInterface dialog) {
1291                            resumeMsg.sendToTarget();
1292                        }
1293                    }).show();
1294            // Return true so that WebViewCore will pause while the dialog is
1295            // up.
1296            rVal = true;
1297        }
1298       return rVal;
1299    }
1300
1301    @Override
1302    public void setScrollBarStyle(int style) {
1303        if (style == View.SCROLLBARS_INSIDE_INSET
1304                || style == View.SCROLLBARS_OUTSIDE_INSET) {
1305            mOverlayHorizontalScrollbar = mOverlayVerticalScrollbar = false;
1306        } else {
1307            mOverlayHorizontalScrollbar = mOverlayVerticalScrollbar = true;
1308        }
1309        super.setScrollBarStyle(style);
1310    }
1311
1312    /**
1313     * Specify whether the horizontal scrollbar has overlay style.
1314     * @param overlay TRUE if horizontal scrollbar should have overlay style.
1315     */
1316    public void setHorizontalScrollbarOverlay(boolean overlay) {
1317        mOverlayHorizontalScrollbar = overlay;
1318    }
1319
1320    /**
1321     * Specify whether the vertical scrollbar has overlay style.
1322     * @param overlay TRUE if vertical scrollbar should have overlay style.
1323     */
1324    public void setVerticalScrollbarOverlay(boolean overlay) {
1325        mOverlayVerticalScrollbar = overlay;
1326    }
1327
1328    /**
1329     * Return whether horizontal scrollbar has overlay style
1330     * @return TRUE if horizontal scrollbar has overlay style.
1331     */
1332    public boolean overlayHorizontalScrollbar() {
1333        return mOverlayHorizontalScrollbar;
1334    }
1335
1336    /**
1337     * Return whether vertical scrollbar has overlay style
1338     * @return TRUE if vertical scrollbar has overlay style.
1339     */
1340    public boolean overlayVerticalScrollbar() {
1341        return mOverlayVerticalScrollbar;
1342    }
1343
1344    /*
1345     * Return the width of the view where the content of WebView should render
1346     * to.
1347     * Note: this can be called from WebCoreThread.
1348     */
1349    /* package */ int getViewWidth() {
1350        if (!isVerticalScrollBarEnabled() || mOverlayVerticalScrollbar) {
1351            return getWidth();
1352        } else {
1353            return Math.max(0, getWidth() - getVerticalScrollbarWidth());
1354        }
1355    }
1356
1357    /**
1358     * returns the height of the titlebarview (if any). Does not care about
1359     * scrolling
1360     * @hide
1361     */
1362    protected int getTitleHeight() {
1363        return mTitleBar != null ? mTitleBar.getHeight() : 0;
1364    }
1365
1366    /**
1367     * Return the amount of the titlebarview (if any) that is visible
1368     *
1369     * @deprecated This method is now obsolete.
1370     */
1371    public int getVisibleTitleHeight() {
1372        // need to restrict mScrollY due to over scroll
1373        return Math.max(getTitleHeight() - Math.max(0, mScrollY), 0);
1374    }
1375
1376    /*
1377     * Return the height of the view where the content of WebView should render
1378     * to.  Note that this excludes mTitleBar, if there is one.
1379     * Note: this can be called from WebCoreThread.
1380     */
1381    /* package */ int getViewHeight() {
1382        return getViewHeightWithTitle() - getVisibleTitleHeight();
1383    }
1384
1385    private int getViewHeightWithTitle() {
1386        int height = getHeight();
1387        if (isHorizontalScrollBarEnabled() && !mOverlayHorizontalScrollbar) {
1388            height -= getHorizontalScrollbarHeight();
1389        }
1390        return height;
1391    }
1392
1393    /**
1394     * @return The SSL certificate for the main top-level page or null if
1395     * there is no certificate (the site is not secure).
1396     */
1397    public SslCertificate getCertificate() {
1398        return mCertificate;
1399    }
1400
1401    /**
1402     * Sets the SSL certificate for the main top-level page.
1403     */
1404    public void setCertificate(SslCertificate certificate) {
1405        if (DebugFlags.WEB_VIEW) {
1406            Log.v(LOGTAG, "setCertificate=" + certificate);
1407        }
1408        // here, the certificate can be null (if the site is not secure)
1409        mCertificate = certificate;
1410    }
1411
1412    //-------------------------------------------------------------------------
1413    // Methods called by activity
1414    //-------------------------------------------------------------------------
1415
1416    /**
1417     * Save the username and password for a particular host in the WebView's
1418     * internal database.
1419     * @param host The host that required the credentials.
1420     * @param username The username for the given host.
1421     * @param password The password for the given host.
1422     */
1423    public void savePassword(String host, String username, String password) {
1424        mDatabase.setUsernamePassword(host, username, password);
1425    }
1426
1427    /**
1428     * Set the HTTP authentication credentials for a given host and realm.
1429     *
1430     * @param host The host for the credentials.
1431     * @param realm The realm for the credentials.
1432     * @param username The username for the password. If it is null, it means
1433     *                 password can't be saved.
1434     * @param password The password
1435     */
1436    public void setHttpAuthUsernamePassword(String host, String realm,
1437            String username, String password) {
1438        mDatabase.setHttpAuthUsernamePassword(host, realm, username, password);
1439    }
1440
1441    /**
1442     * Retrieve the HTTP authentication username and password for a given
1443     * host & realm pair
1444     *
1445     * @param host The host for which the credentials apply.
1446     * @param realm The realm for which the credentials apply.
1447     * @return String[] if found, String[0] is username, which can be null and
1448     *         String[1] is password. Return null if it can't find anything.
1449     */
1450    public String[] getHttpAuthUsernamePassword(String host, String realm) {
1451        return mDatabase.getHttpAuthUsernamePassword(host, realm);
1452    }
1453
1454    /**
1455     * Remove Find or Select ActionModes, if active.
1456     */
1457    private void clearActionModes() {
1458        if (mSelectCallback != null) {
1459            mSelectCallback.finish();
1460        }
1461        if (mFindCallback != null) {
1462            mFindCallback.finish();
1463        }
1464    }
1465
1466    /**
1467     * Called to clear state when moving from one page to another, or changing
1468     * in some other way that makes elements associated with the current page
1469     * (such as WebTextView or ActionModes) no longer relevant.
1470     */
1471    private void clearHelpers() {
1472        clearTextEntry();
1473        clearActionModes();
1474        dismissFullScreenMode();
1475    }
1476
1477    /**
1478     * Destroy the internal state of the WebView. This method should be called
1479     * after the WebView has been removed from the view system. No other
1480     * methods may be called on a WebView after destroy.
1481     */
1482    public void destroy() {
1483        clearHelpers();
1484        if (mListBoxDialog != null) {
1485            mListBoxDialog.dismiss();
1486            mListBoxDialog = null;
1487        }
1488        if (mNativeClass != 0) nativeStopGL();
1489        if (mWebViewCore != null) {
1490            // Set the handlers to null before destroying WebViewCore so no
1491            // more messages will be posted.
1492            mCallbackProxy.setWebViewClient(null);
1493            mCallbackProxy.setWebChromeClient(null);
1494            // Tell WebViewCore to destroy itself
1495            synchronized (this) {
1496                WebViewCore webViewCore = mWebViewCore;
1497                mWebViewCore = null; // prevent using partial webViewCore
1498                webViewCore.destroy();
1499            }
1500            // Remove any pending messages that might not be serviced yet.
1501            mPrivateHandler.removeCallbacksAndMessages(null);
1502            mCallbackProxy.removeCallbacksAndMessages(null);
1503            // Wake up the WebCore thread just in case it is waiting for a
1504            // JavaScript dialog.
1505            synchronized (mCallbackProxy) {
1506                mCallbackProxy.notify();
1507            }
1508        }
1509        if (mNativeClass != 0) {
1510            nativeDestroy();
1511            mNativeClass = 0;
1512        }
1513    }
1514
1515    /**
1516     * Enables platform notifications of data state and proxy changes.
1517     */
1518    public static void enablePlatformNotifications() {
1519        Network.enablePlatformNotifications();
1520    }
1521
1522    /**
1523     * If platform notifications are enabled, this should be called
1524     * from the Activity's onPause() or onStop().
1525     */
1526    public static void disablePlatformNotifications() {
1527        Network.disablePlatformNotifications();
1528    }
1529
1530    /**
1531     * Sets JavaScript engine flags.
1532     *
1533     * @param flags JS engine flags in a String
1534     *
1535     * @hide pending API solidification
1536     */
1537    public void setJsFlags(String flags) {
1538        mWebViewCore.sendMessage(EventHub.SET_JS_FLAGS, flags);
1539    }
1540
1541    /**
1542     * Inform WebView of the network state. This is used to set
1543     * the JavaScript property window.navigator.isOnline and
1544     * generates the online/offline event as specified in HTML5, sec. 5.7.7
1545     * @param networkUp boolean indicating if network is available
1546     */
1547    public void setNetworkAvailable(boolean networkUp) {
1548        mWebViewCore.sendMessage(EventHub.SET_NETWORK_STATE,
1549                networkUp ? 1 : 0, 0);
1550    }
1551
1552    /**
1553     * Inform WebView about the current network type.
1554     * {@hide}
1555     */
1556    public void setNetworkType(String type, String subtype) {
1557        Map<String, String> map = new HashMap<String, String>();
1558        map.put("type", type);
1559        map.put("subtype", subtype);
1560        mWebViewCore.sendMessage(EventHub.SET_NETWORK_TYPE, map);
1561    }
1562    /**
1563     * Save the state of this WebView used in
1564     * {@link android.app.Activity#onSaveInstanceState}. Please note that this
1565     * method no longer stores the display data for this WebView. The previous
1566     * behavior could potentially leak files if {@link #restoreState} was never
1567     * called. See {@link #savePicture} and {@link #restorePicture} for saving
1568     * and restoring the display data.
1569     * @param outState The Bundle to store the WebView state.
1570     * @return The same copy of the back/forward list used to save the state. If
1571     *         saveState fails, the returned list will be null.
1572     * @see #savePicture
1573     * @see #restorePicture
1574     */
1575    public WebBackForwardList saveState(Bundle outState) {
1576        if (outState == null) {
1577            return null;
1578        }
1579        // We grab a copy of the back/forward list because a client of WebView
1580        // may have invalidated the history list by calling clearHistory.
1581        WebBackForwardList list = copyBackForwardList();
1582        final int currentIndex = list.getCurrentIndex();
1583        final int size = list.getSize();
1584        // We should fail saving the state if the list is empty or the index is
1585        // not in a valid range.
1586        if (currentIndex < 0 || currentIndex >= size || size == 0) {
1587            return null;
1588        }
1589        outState.putInt("index", currentIndex);
1590        // FIXME: This should just be a byte[][] instead of ArrayList but
1591        // Parcel.java does not have the code to handle multi-dimensional
1592        // arrays.
1593        ArrayList<byte[]> history = new ArrayList<byte[]>(size);
1594        for (int i = 0; i < size; i++) {
1595            WebHistoryItem item = list.getItemAtIndex(i);
1596            if (null == item) {
1597                // FIXME: this shouldn't happen
1598                // need to determine how item got set to null
1599                Log.w(LOGTAG, "saveState: Unexpected null history item.");
1600                return null;
1601            }
1602            byte[] data = item.getFlattenedData();
1603            if (data == null) {
1604                // It would be very odd to not have any data for a given history
1605                // item. And we will fail to rebuild the history list without
1606                // flattened data.
1607                return null;
1608            }
1609            history.add(data);
1610        }
1611        outState.putSerializable("history", history);
1612        if (mCertificate != null) {
1613            outState.putBundle("certificate",
1614                               SslCertificate.saveState(mCertificate));
1615        }
1616        outState.putBoolean("privateBrowsingEnabled", isPrivateBrowsingEnabled());
1617        mZoomManager.saveZoomState(outState);
1618        return list;
1619    }
1620
1621    /**
1622     * Save the current display data to the Bundle given. Used in conjunction
1623     * with {@link #saveState}.
1624     * @param b A Bundle to store the display data.
1625     * @param dest The file to store the serialized picture data. Will be
1626     *             overwritten with this WebView's picture data.
1627     * @return True if the picture was successfully saved.
1628     */
1629    public boolean savePicture(Bundle b, final File dest) {
1630        if (dest == null || b == null) {
1631            return false;
1632        }
1633        final Picture p = capturePicture();
1634        // Use a temporary file while writing to ensure the destination file
1635        // contains valid data.
1636        final File temp = new File(dest.getPath() + ".writing");
1637        new Thread(new Runnable() {
1638            public void run() {
1639                FileOutputStream out = null;
1640                try {
1641                    out = new FileOutputStream(temp);
1642                    p.writeToStream(out);
1643                    // Writing the picture succeeded, rename the temporary file
1644                    // to the destination.
1645                    temp.renameTo(dest);
1646                } catch (Exception e) {
1647                    // too late to do anything about it.
1648                } finally {
1649                    if (out != null) {
1650                        try {
1651                            out.close();
1652                        } catch (Exception e) {
1653                            // Can't do anything about that
1654                        }
1655                    }
1656                    temp.delete();
1657                }
1658            }
1659        }).start();
1660        // now update the bundle
1661        b.putInt("scrollX", mScrollX);
1662        b.putInt("scrollY", mScrollY);
1663        mZoomManager.saveZoomState(b);
1664        return true;
1665    }
1666
1667    private void restoreHistoryPictureFields(Picture p, Bundle b) {
1668        int sx = b.getInt("scrollX", 0);
1669        int sy = b.getInt("scrollY", 0);
1670
1671        mDrawHistory = true;
1672        mHistoryPicture = p;
1673
1674        mScrollX = sx;
1675        mScrollY = sy;
1676        mZoomManager.restoreZoomState(b);
1677        final float scale = mZoomManager.getScale();
1678        mHistoryWidth = Math.round(p.getWidth() * scale);
1679        mHistoryHeight = Math.round(p.getHeight() * scale);
1680
1681        invalidate();
1682    }
1683
1684    /**
1685     * Restore the display data that was save in {@link #savePicture}. Used in
1686     * conjunction with {@link #restoreState}.
1687     * @param b A Bundle containing the saved display data.
1688     * @param src The file where the picture data was stored.
1689     * @return True if the picture was successfully restored.
1690     */
1691    public boolean restorePicture(Bundle b, File src) {
1692        if (src == null || b == null) {
1693            return false;
1694        }
1695        if (!src.exists()) {
1696            return false;
1697        }
1698        try {
1699            final FileInputStream in = new FileInputStream(src);
1700            final Bundle copy = new Bundle(b);
1701            new Thread(new Runnable() {
1702                public void run() {
1703                    try {
1704                        final Picture p = Picture.createFromStream(in);
1705                        if (p != null) {
1706                            // Post a runnable on the main thread to update the
1707                            // history picture fields.
1708                            mPrivateHandler.post(new Runnable() {
1709                                public void run() {
1710                                    restoreHistoryPictureFields(p, copy);
1711                                }
1712                            });
1713                        }
1714                    } finally {
1715                        try {
1716                            in.close();
1717                        } catch (Exception e) {
1718                            // Nothing we can do now.
1719                        }
1720                    }
1721                }
1722            }).start();
1723        } catch (FileNotFoundException e){
1724            e.printStackTrace();
1725        }
1726        return true;
1727    }
1728
1729    /**
1730     * Restore the state of this WebView from the given map used in
1731     * {@link android.app.Activity#onRestoreInstanceState}. This method should
1732     * be called to restore the state of the WebView before using the object. If
1733     * it is called after the WebView has had a chance to build state (load
1734     * pages, create a back/forward list, etc.) there may be undesirable
1735     * side-effects. Please note that this method no longer restores the
1736     * display data for this WebView. See {@link #savePicture} and {@link
1737     * #restorePicture} for saving and restoring the display data.
1738     * @param inState The incoming Bundle of state.
1739     * @return The restored back/forward list or null if restoreState failed.
1740     * @see #savePicture
1741     * @see #restorePicture
1742     */
1743    public WebBackForwardList restoreState(Bundle inState) {
1744        WebBackForwardList returnList = null;
1745        if (inState == null) {
1746            return returnList;
1747        }
1748        if (inState.containsKey("index") && inState.containsKey("history")) {
1749            mCertificate = SslCertificate.restoreState(
1750                inState.getBundle("certificate"));
1751
1752            final WebBackForwardList list = mCallbackProxy.getBackForwardList();
1753            final int index = inState.getInt("index");
1754            // We can't use a clone of the list because we need to modify the
1755            // shared copy, so synchronize instead to prevent concurrent
1756            // modifications.
1757            synchronized (list) {
1758                final List<byte[]> history =
1759                        (List<byte[]>) inState.getSerializable("history");
1760                final int size = history.size();
1761                // Check the index bounds so we don't crash in native code while
1762                // restoring the history index.
1763                if (index < 0 || index >= size) {
1764                    return null;
1765                }
1766                for (int i = 0; i < size; i++) {
1767                    byte[] data = history.remove(0);
1768                    if (data == null) {
1769                        // If we somehow have null data, we cannot reconstruct
1770                        // the item and thus our history list cannot be rebuilt.
1771                        return null;
1772                    }
1773                    WebHistoryItem item = new WebHistoryItem(data);
1774                    list.addHistoryItem(item);
1775                }
1776                // Grab the most recent copy to return to the caller.
1777                returnList = copyBackForwardList();
1778                // Update the copy to have the correct index.
1779                returnList.setCurrentIndex(index);
1780            }
1781            // Restore private browsing setting.
1782            if (inState.getBoolean("privateBrowsingEnabled")) {
1783                getSettings().setPrivateBrowsingEnabled(true);
1784            }
1785            mZoomManager.restoreZoomState(inState);
1786            // Remove all pending messages because we are restoring previous
1787            // state.
1788            mWebViewCore.removeMessages();
1789            // Send a restore state message.
1790            mWebViewCore.sendMessage(EventHub.RESTORE_STATE, index);
1791        }
1792        return returnList;
1793    }
1794
1795    /**
1796     * Load the given url with the extra headers.
1797     * @param url The url of the resource to load.
1798     * @param extraHeaders The extra headers sent with this url. This should not
1799     *            include the common headers like "user-agent". If it does, it
1800     *            will be replaced by the intrinsic value of the WebView.
1801     */
1802    public void loadUrl(String url, Map<String, String> extraHeaders) {
1803        switchOutDrawHistory();
1804        WebViewCore.GetUrlData arg = new WebViewCore.GetUrlData();
1805        arg.mUrl = url;
1806        arg.mExtraHeaders = extraHeaders;
1807        mWebViewCore.sendMessage(EventHub.LOAD_URL, arg);
1808        clearHelpers();
1809    }
1810
1811    /**
1812     * Load the given url.
1813     * @param url The url of the resource to load.
1814     */
1815    public void loadUrl(String url) {
1816        if (url == null) {
1817            return;
1818        }
1819        loadUrl(url, null);
1820    }
1821
1822    /**
1823     * Load the url with postData using "POST" method into the WebView. If url
1824     * is not a network url, it will be loaded with {link
1825     * {@link #loadUrl(String)} instead.
1826     *
1827     * @param url The url of the resource to load.
1828     * @param postData The data will be passed to "POST" request.
1829     */
1830    public void postUrl(String url, byte[] postData) {
1831        if (URLUtil.isNetworkUrl(url)) {
1832            switchOutDrawHistory();
1833            WebViewCore.PostUrlData arg = new WebViewCore.PostUrlData();
1834            arg.mUrl = url;
1835            arg.mPostData = postData;
1836            mWebViewCore.sendMessage(EventHub.POST_URL, arg);
1837            clearHelpers();
1838        } else {
1839            loadUrl(url);
1840        }
1841    }
1842
1843    /**
1844     * Load the given data into the WebView. This will load the data into
1845     * WebView using the data: scheme. Content loaded through this mechanism
1846     * does not have the ability to load content from the network.
1847     * @param data A String of data in the given encoding. The date must
1848     * be URI-escaped -- '#', '%', '\', '?' should be replaced by %23, %25,
1849     * %27, %3f respectively.
1850     * @param mimeType The MIMEType of the data. i.e. text/html, image/jpeg
1851     * @param encoding The encoding of the data. i.e. utf-8, base64
1852     */
1853    public void loadData(String data, String mimeType, String encoding) {
1854        loadUrl("data:" + mimeType + ";" + encoding + "," + data);
1855    }
1856
1857    /**
1858     * Load the given data into the WebView, use the provided URL as the base
1859     * URL for the content. The base URL is the URL that represents the page
1860     * that is loaded through this interface. As such, it is used to resolve any
1861     * relative URLs. The historyUrl is used for the history entry.
1862     * <p>
1863     * Note for post 1.0. Due to the change in the WebKit, the access to asset
1864     * files through "file:///android_asset/" for the sub resources is more
1865     * restricted. If you provide null or empty string as baseUrl, you won't be
1866     * able to access asset files. If the baseUrl is anything other than
1867     * http(s)/ftp(s)/about/javascript as scheme, you can access asset files for
1868     * sub resources.
1869     *
1870     * @param baseUrl Url to resolve relative paths with, if null defaults to
1871     *            "about:blank"
1872     * @param data A String of data in the given encoding.
1873     * @param mimeType The MIMEType of the data. i.e. text/html. If null,
1874     *            defaults to "text/html"
1875     * @param encoding The encoding of the data. i.e. utf-8, us-ascii
1876     * @param historyUrl URL to use as the history entry.  Can be null.
1877     */
1878    public void loadDataWithBaseURL(String baseUrl, String data,
1879            String mimeType, String encoding, String historyUrl) {
1880
1881        if (baseUrl != null && baseUrl.toLowerCase().startsWith("data:")) {
1882            loadData(data, mimeType, encoding);
1883            return;
1884        }
1885        switchOutDrawHistory();
1886        WebViewCore.BaseUrlData arg = new WebViewCore.BaseUrlData();
1887        arg.mBaseUrl = baseUrl;
1888        arg.mData = data;
1889        arg.mMimeType = mimeType;
1890        arg.mEncoding = encoding;
1891        arg.mHistoryUrl = historyUrl;
1892        mWebViewCore.sendMessage(EventHub.LOAD_DATA, arg);
1893        clearHelpers();
1894    }
1895
1896    /**
1897     * Saves the current view as a web archive.
1898     *
1899     * @param filename The filename where the archive should be placed.
1900     */
1901    public void saveWebArchive(String filename) {
1902        saveWebArchive(filename, false, null);
1903    }
1904
1905    /* package */ static class SaveWebArchiveMessage {
1906        SaveWebArchiveMessage (String basename, boolean autoname, ValueCallback<String> callback) {
1907            mBasename = basename;
1908            mAutoname = autoname;
1909            mCallback = callback;
1910        }
1911
1912        /* package */ final String mBasename;
1913        /* package */ final boolean mAutoname;
1914        /* package */ final ValueCallback<String> mCallback;
1915        /* package */ String mResultFile;
1916    }
1917
1918    /**
1919     * Saves the current view as a web archive.
1920     *
1921     * @param basename The filename where the archive should be placed.
1922     * @param autoname If false, takes basename to be a file. If true, basename
1923     *                 is assumed to be a directory in which a filename will be
1924     *                 chosen according to the url of the current page.
1925     * @param callback Called after the web archive has been saved. The
1926     *                 parameter for onReceiveValue will either be the filename
1927     *                 under which the file was saved, or null if saving the
1928     *                 file failed.
1929     */
1930    public void saveWebArchive(String basename, boolean autoname, ValueCallback<String> callback) {
1931        mWebViewCore.sendMessage(EventHub.SAVE_WEBARCHIVE,
1932            new SaveWebArchiveMessage(basename, autoname, callback));
1933    }
1934
1935    /**
1936     * Stop the current load.
1937     */
1938    public void stopLoading() {
1939        // TODO: should we clear all the messages in the queue before sending
1940        // STOP_LOADING?
1941        switchOutDrawHistory();
1942        mWebViewCore.sendMessage(EventHub.STOP_LOADING);
1943    }
1944
1945    /**
1946     * Reload the current url.
1947     */
1948    public void reload() {
1949        clearHelpers();
1950        switchOutDrawHistory();
1951        mWebViewCore.sendMessage(EventHub.RELOAD);
1952    }
1953
1954    /**
1955     * Return true if this WebView has a back history item.
1956     * @return True iff this WebView has a back history item.
1957     */
1958    public boolean canGoBack() {
1959        WebBackForwardList l = mCallbackProxy.getBackForwardList();
1960        synchronized (l) {
1961            if (l.getClearPending()) {
1962                return false;
1963            } else {
1964                return l.getCurrentIndex() > 0;
1965            }
1966        }
1967    }
1968
1969    /**
1970     * Go back in the history of this WebView.
1971     */
1972    public void goBack() {
1973        goBackOrForward(-1);
1974    }
1975
1976    /**
1977     * Return true if this WebView has a forward history item.
1978     * @return True iff this Webview has a forward history item.
1979     */
1980    public boolean canGoForward() {
1981        WebBackForwardList l = mCallbackProxy.getBackForwardList();
1982        synchronized (l) {
1983            if (l.getClearPending()) {
1984                return false;
1985            } else {
1986                return l.getCurrentIndex() < l.getSize() - 1;
1987            }
1988        }
1989    }
1990
1991    /**
1992     * Go forward in the history of this WebView.
1993     */
1994    public void goForward() {
1995        goBackOrForward(1);
1996    }
1997
1998    /**
1999     * Return true if the page can go back or forward the given
2000     * number of steps.
2001     * @param steps The negative or positive number of steps to move the
2002     *              history.
2003     */
2004    public boolean canGoBackOrForward(int steps) {
2005        WebBackForwardList l = mCallbackProxy.getBackForwardList();
2006        synchronized (l) {
2007            if (l.getClearPending()) {
2008                return false;
2009            } else {
2010                int newIndex = l.getCurrentIndex() + steps;
2011                return newIndex >= 0 && newIndex < l.getSize();
2012            }
2013        }
2014    }
2015
2016    /**
2017     * Go to the history item that is the number of steps away from
2018     * the current item. Steps is negative if backward and positive
2019     * if forward.
2020     * @param steps The number of steps to take back or forward in the back
2021     *              forward list.
2022     */
2023    public void goBackOrForward(int steps) {
2024        goBackOrForward(steps, false);
2025    }
2026
2027    private void goBackOrForward(int steps, boolean ignoreSnapshot) {
2028        if (steps != 0) {
2029            clearHelpers();
2030            mWebViewCore.sendMessage(EventHub.GO_BACK_FORWARD, steps,
2031                    ignoreSnapshot ? 1 : 0);
2032        }
2033    }
2034
2035    /**
2036     * Returns true if private browsing is enabled in this WebView.
2037     */
2038    public boolean isPrivateBrowsingEnabled() {
2039        return getSettings().isPrivateBrowsingEnabled();
2040    }
2041
2042    private void startPrivateBrowsing() {
2043        getSettings().setPrivateBrowsingEnabled(true);
2044    }
2045
2046    private boolean extendScroll(int y) {
2047        int finalY = mScroller.getFinalY();
2048        int newY = pinLocY(finalY + y);
2049        if (newY == finalY) return false;
2050        mScroller.setFinalY(newY);
2051        mScroller.extendDuration(computeDuration(0, y));
2052        return true;
2053    }
2054
2055    /**
2056     * Scroll the contents of the view up by half the view size
2057     * @param top true to jump to the top of the page
2058     * @return true if the page was scrolled
2059     */
2060    public boolean pageUp(boolean top) {
2061        if (mNativeClass == 0) {
2062            return false;
2063        }
2064        nativeClearCursor(); // start next trackball movement from page edge
2065        if (top) {
2066            // go to the top of the document
2067            return pinScrollTo(mScrollX, 0, true, 0);
2068        }
2069        // Page up
2070        int h = getHeight();
2071        int y;
2072        if (h > 2 * PAGE_SCROLL_OVERLAP) {
2073            y = -h + PAGE_SCROLL_OVERLAP;
2074        } else {
2075            y = -h / 2;
2076        }
2077        return mScroller.isFinished() ? pinScrollBy(0, y, true, 0)
2078                : extendScroll(y);
2079    }
2080
2081    /**
2082     * Scroll the contents of the view down by half the page size
2083     * @param bottom true to jump to bottom of page
2084     * @return true if the page was scrolled
2085     */
2086    public boolean pageDown(boolean bottom) {
2087        if (mNativeClass == 0) {
2088            return false;
2089        }
2090        nativeClearCursor(); // start next trackball movement from page edge
2091        if (bottom) {
2092            return pinScrollTo(mScrollX, computeRealVerticalScrollRange(), true, 0);
2093        }
2094        // Page down.
2095        int h = getHeight();
2096        int y;
2097        if (h > 2 * PAGE_SCROLL_OVERLAP) {
2098            y = h - PAGE_SCROLL_OVERLAP;
2099        } else {
2100            y = h / 2;
2101        }
2102        return mScroller.isFinished() ? pinScrollBy(0, y, true, 0)
2103                : extendScroll(y);
2104    }
2105
2106    /**
2107     * Clear the view so that onDraw() will draw nothing but white background,
2108     * and onMeasure() will return 0 if MeasureSpec is not MeasureSpec.EXACTLY
2109     */
2110    public void clearView() {
2111        mContentWidth = 0;
2112        mContentHeight = 0;
2113        setBaseLayer(0, null, false, false);
2114        mWebViewCore.sendMessage(EventHub.CLEAR_CONTENT);
2115    }
2116
2117    /**
2118     * Return a new picture that captures the current display of the webview.
2119     * This is a copy of the display, and will be unaffected if the webview
2120     * later loads a different URL.
2121     *
2122     * @return a picture containing the current contents of the view. Note this
2123     *         picture is of the entire document, and is not restricted to the
2124     *         bounds of the view.
2125     */
2126    public Picture capturePicture() {
2127        if (mNativeClass == 0) return null;
2128        Picture result = new Picture();
2129        nativeCopyBaseContentToPicture(result);
2130        return result;
2131    }
2132
2133    /**
2134     *  Return true if the browser is displaying a TextView for text input.
2135     */
2136    private boolean inEditingMode() {
2137        return mWebTextView != null && mWebTextView.getParent() != null;
2138    }
2139
2140    /**
2141     * Remove the WebTextView.
2142     */
2143    private void clearTextEntry() {
2144        if (inEditingMode()) {
2145            mWebTextView.remove();
2146        } else {
2147            // The keyboard may be open with the WebView as the served view
2148            hideSoftKeyboard();
2149        }
2150    }
2151
2152    /**
2153     * Return the current scale of the WebView
2154     * @return The current scale.
2155     */
2156    public float getScale() {
2157        return mZoomManager.getScale();
2158    }
2159
2160    /**
2161     * Set the initial scale for the WebView. 0 means default. If
2162     * {@link WebSettings#getUseWideViewPort()} is true, it zooms out all the
2163     * way. Otherwise it starts with 100%. If initial scale is greater than 0,
2164     * WebView starts will this value as initial scale.
2165     *
2166     * @param scaleInPercent The initial scale in percent.
2167     */
2168    public void setInitialScale(int scaleInPercent) {
2169        mZoomManager.setInitialScaleInPercent(scaleInPercent);
2170    }
2171
2172    /**
2173     * Invoke the graphical zoom picker widget for this WebView. This will
2174     * result in the zoom widget appearing on the screen to control the zoom
2175     * level of this WebView.
2176     */
2177    public void invokeZoomPicker() {
2178        if (!getSettings().supportZoom()) {
2179            Log.w(LOGTAG, "This WebView doesn't support zoom.");
2180            return;
2181        }
2182        clearHelpers();
2183        mZoomManager.invokeZoomPicker();
2184    }
2185
2186    /**
2187     * Return a HitTestResult based on the current cursor node. If a HTML::a tag
2188     * is found and the anchor has a non-JavaScript url, the HitTestResult type
2189     * is set to SRC_ANCHOR_TYPE and the url is set in the "extra" field. If the
2190     * anchor does not have a url or if it is a JavaScript url, the type will
2191     * be UNKNOWN_TYPE and the url has to be retrieved through
2192     * {@link #requestFocusNodeHref} asynchronously. If a HTML::img tag is
2193     * found, the HitTestResult type is set to IMAGE_TYPE and the url is set in
2194     * the "extra" field. A type of
2195     * SRC_IMAGE_ANCHOR_TYPE indicates an anchor with a url that has an image as
2196     * a child node. If a phone number is found, the HitTestResult type is set
2197     * to PHONE_TYPE and the phone number is set in the "extra" field of
2198     * HitTestResult. If a map address is found, the HitTestResult type is set
2199     * to GEO_TYPE and the address is set in the "extra" field of HitTestResult.
2200     * If an email address is found, the HitTestResult type is set to EMAIL_TYPE
2201     * and the email is set in the "extra" field of HitTestResult. Otherwise,
2202     * HitTestResult type is set to UNKNOWN_TYPE.
2203     */
2204    public HitTestResult getHitTestResult() {
2205        return hitTestResult(mInitialHitTestResult);
2206    }
2207
2208    private HitTestResult hitTestResult(HitTestResult fallback) {
2209        if (mNativeClass == 0) {
2210            return null;
2211        }
2212
2213        HitTestResult result = new HitTestResult();
2214        if (nativeHasCursorNode()) {
2215            if (nativeCursorIsTextInput()) {
2216                result.setType(HitTestResult.EDIT_TEXT_TYPE);
2217            } else {
2218                String text = nativeCursorText();
2219                if (text != null) {
2220                    if (text.startsWith(SCHEME_TEL)) {
2221                        result.setType(HitTestResult.PHONE_TYPE);
2222                        result.setExtra(text.substring(SCHEME_TEL.length()));
2223                    } else if (text.startsWith(SCHEME_MAILTO)) {
2224                        result.setType(HitTestResult.EMAIL_TYPE);
2225                        result.setExtra(text.substring(SCHEME_MAILTO.length()));
2226                    } else if (text.startsWith(SCHEME_GEO)) {
2227                        result.setType(HitTestResult.GEO_TYPE);
2228                        result.setExtra(URLDecoder.decode(text
2229                                .substring(SCHEME_GEO.length())));
2230                    } else if (nativeCursorIsAnchor()) {
2231                        result.setType(HitTestResult.SRC_ANCHOR_TYPE);
2232                        result.setExtra(text);
2233                    }
2234                }
2235            }
2236        } else if (fallback != null) {
2237            /* If webkit causes a rebuild while the long press is in progress,
2238             * the cursor node may be reset, even if it is still around. This
2239             * uses the cursor node saved when the touch began. Since the
2240             * nativeImageURI below only changes the result if it is successful,
2241             * this uses the data beneath the touch if available or the original
2242             * tap data otherwise.
2243             */
2244            Log.v(LOGTAG, "hitTestResult use fallback");
2245            result = fallback;
2246        }
2247        int type = result.getType();
2248        if (type == HitTestResult.UNKNOWN_TYPE
2249                || type == HitTestResult.SRC_ANCHOR_TYPE) {
2250            // Now check to see if it is an image.
2251            int contentX = viewToContentX(mLastTouchX + mScrollX);
2252            int contentY = viewToContentY(mLastTouchY + mScrollY);
2253            String text = nativeImageURI(contentX, contentY);
2254            if (text != null) {
2255                result.setType(type == HitTestResult.UNKNOWN_TYPE ?
2256                        HitTestResult.IMAGE_TYPE :
2257                        HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
2258                result.setExtra(text);
2259            }
2260        }
2261        return result;
2262    }
2263
2264    // Called by JNI when the DOM has changed the focus.  Clear the focus so
2265    // that new keys will go to the newly focused field
2266    private void domChangedFocus() {
2267        if (inEditingMode()) {
2268            mPrivateHandler.obtainMessage(DOM_FOCUS_CHANGED).sendToTarget();
2269        }
2270    }
2271    /**
2272     * Request the anchor or image element URL at the last tapped point.
2273     * If hrefMsg is null, this method returns immediately and does not
2274     * dispatch hrefMsg to its target. If the tapped point hits an image,
2275     * an anchor, or an image in an anchor, the message associates
2276     * strings in named keys in its data. The value paired with the key
2277     * may be an empty string.
2278     *
2279     * @param hrefMsg This message will be dispatched with the result of the
2280     *                request. The message data contains three keys:
2281     *                - "url" returns the anchor's href attribute.
2282     *                - "title" returns the anchor's text.
2283     *                - "src" returns the image's src attribute.
2284     */
2285    public void requestFocusNodeHref(Message hrefMsg) {
2286        if (hrefMsg == null) {
2287            return;
2288        }
2289        int contentX = viewToContentX(mLastTouchX + mScrollX);
2290        int contentY = viewToContentY(mLastTouchY + mScrollY);
2291        if (nativeHasCursorNode()) {
2292            Rect cursorBounds = nativeGetCursorRingBounds();
2293            if (!cursorBounds.contains(contentX, contentY)) {
2294                int slop = viewToContentDimension(mNavSlop);
2295                cursorBounds.inset(-slop, -slop);
2296                if (cursorBounds.contains(contentX, contentY)) {
2297                    contentX = (int) cursorBounds.centerX();
2298                    contentY = (int) cursorBounds.centerY();
2299                }
2300            }
2301        }
2302        mWebViewCore.sendMessage(EventHub.REQUEST_CURSOR_HREF,
2303                contentX, contentY, hrefMsg);
2304    }
2305
2306    /**
2307     * Request the url of the image last touched by the user. msg will be sent
2308     * to its target with a String representing the url as its object.
2309     *
2310     * @param msg This message will be dispatched with the result of the request
2311     *            as the data member with "url" as key. The result can be null.
2312     */
2313    public void requestImageRef(Message msg) {
2314        if (0 == mNativeClass) return; // client isn't initialized
2315        int contentX = viewToContentX(mLastTouchX + mScrollX);
2316        int contentY = viewToContentY(mLastTouchY + mScrollY);
2317        String ref = nativeImageURI(contentX, contentY);
2318        Bundle data = msg.getData();
2319        data.putString("url", ref);
2320        msg.setData(data);
2321        msg.sendToTarget();
2322    }
2323
2324    static int pinLoc(int x, int viewMax, int docMax) {
2325//        Log.d(LOGTAG, "-- pinLoc " + x + " " + viewMax + " " + docMax);
2326        if (docMax < viewMax) {   // the doc has room on the sides for "blank"
2327            // pin the short document to the top/left of the screen
2328            x = 0;
2329//            Log.d(LOGTAG, "--- center " + x);
2330        } else if (x < 0) {
2331            x = 0;
2332//            Log.d(LOGTAG, "--- zero");
2333        } else if (x + viewMax > docMax) {
2334            x = docMax - viewMax;
2335//            Log.d(LOGTAG, "--- pin " + x);
2336        }
2337        return x;
2338    }
2339
2340    // Expects x in view coordinates
2341    int pinLocX(int x) {
2342        if (mInOverScrollMode) return x;
2343        return pinLoc(x, getViewWidth(), computeRealHorizontalScrollRange());
2344    }
2345
2346    // Expects y in view coordinates
2347    int pinLocY(int y) {
2348        if (mInOverScrollMode) return y;
2349        return pinLoc(y, getViewHeightWithTitle(),
2350                      computeRealVerticalScrollRange() + getTitleHeight());
2351    }
2352
2353    /**
2354     * A title bar which is embedded in this WebView, and scrolls along with it
2355     * vertically, but not horizontally.
2356     */
2357    private View mTitleBar;
2358
2359    /**
2360     * the title bar rendering gravity
2361     */
2362    private int mTitleGravity;
2363
2364    /**
2365     * Add or remove a title bar to be embedded into the WebView, and scroll
2366     * along with it vertically, while remaining in view horizontally. Pass
2367     * null to remove the title bar from the WebView, and return to drawing
2368     * the WebView normally without translating to account for the title bar.
2369     * @hide
2370     */
2371    public void setEmbeddedTitleBar(View v) {
2372        if (mTitleBar == v) return;
2373        if (mTitleBar != null) {
2374            removeView(mTitleBar);
2375        }
2376        if (null != v) {
2377            addView(v, new AbsoluteLayout.LayoutParams(
2378                    ViewGroup.LayoutParams.MATCH_PARENT,
2379                    ViewGroup.LayoutParams.WRAP_CONTENT, 0, 0));
2380        }
2381        mTitleBar = v;
2382    }
2383
2384    /**
2385     * Set where to render the embedded title bar
2386     * NO_GRAVITY at the top of the page
2387     * TOP        at the top of the screen
2388     * @hide
2389     */
2390    public void setTitleBarGravity(int gravity) {
2391        mTitleGravity = gravity;
2392        // force refresh
2393        invalidate();
2394    }
2395
2396    /**
2397     * Given a distance in view space, convert it to content space. Note: this
2398     * does not reflect translation, just scaling, so this should not be called
2399     * with coordinates, but should be called for dimensions like width or
2400     * height.
2401     */
2402    private int viewToContentDimension(int d) {
2403        return Math.round(d * mZoomManager.getInvScale());
2404    }
2405
2406    /**
2407     * Given an x coordinate in view space, convert it to content space.  Also
2408     * may be used for absolute heights (such as for the WebTextView's
2409     * textSize, which is unaffected by the height of the title bar).
2410     */
2411    /*package*/ int viewToContentX(int x) {
2412        return viewToContentDimension(x);
2413    }
2414
2415    /**
2416     * Given a y coordinate in view space, convert it to content space.
2417     * Takes into account the height of the title bar if there is one
2418     * embedded into the WebView.
2419     */
2420    /*package*/ int viewToContentY(int y) {
2421        return viewToContentDimension(y - getTitleHeight());
2422    }
2423
2424    /**
2425     * Given a x coordinate in view space, convert it to content space.
2426     * Returns the result as a float.
2427     */
2428    private float viewToContentXf(int x) {
2429        return x * mZoomManager.getInvScale();
2430    }
2431
2432    /**
2433     * Given a y coordinate in view space, convert it to content space.
2434     * Takes into account the height of the title bar if there is one
2435     * embedded into the WebView. Returns the result as a float.
2436     */
2437    private float viewToContentYf(int y) {
2438        return (y - getTitleHeight()) * mZoomManager.getInvScale();
2439    }
2440
2441    /**
2442     * Given a distance in content space, convert it to view space. Note: this
2443     * does not reflect translation, just scaling, so this should not be called
2444     * with coordinates, but should be called for dimensions like width or
2445     * height.
2446     */
2447    /*package*/ int contentToViewDimension(int d) {
2448        return Math.round(d * mZoomManager.getScale());
2449    }
2450
2451    /**
2452     * Given an x coordinate in content space, convert it to view
2453     * space.
2454     */
2455    /*package*/ int contentToViewX(int x) {
2456        return contentToViewDimension(x);
2457    }
2458
2459    /**
2460     * Given a y coordinate in content space, convert it to view
2461     * space.  Takes into account the height of the title bar.
2462     */
2463    /*package*/ int contentToViewY(int y) {
2464        return contentToViewDimension(y) + getTitleHeight();
2465    }
2466
2467    private Rect contentToViewRect(Rect x) {
2468        return new Rect(contentToViewX(x.left), contentToViewY(x.top),
2469                        contentToViewX(x.right), contentToViewY(x.bottom));
2470    }
2471
2472    /*  To invalidate a rectangle in content coordinates, we need to transform
2473        the rect into view coordinates, so we can then call invalidate(...).
2474
2475        Normally, we would just call contentToView[XY](...), which eventually
2476        calls Math.round(coordinate * mActualScale). However, for invalidates,
2477        we need to account for the slop that occurs with antialiasing. To
2478        address that, we are a little more liberal in the size of the rect that
2479        we invalidate.
2480
2481        This liberal calculation calls floor() for the top/left, and ceil() for
2482        the bottom/right coordinates. This catches the possible extra pixels of
2483        antialiasing that we might have missed with just round().
2484     */
2485
2486    // Called by JNI to invalidate the View, given rectangle coordinates in
2487    // content space
2488    private void viewInvalidate(int l, int t, int r, int b) {
2489        final float scale = mZoomManager.getScale();
2490        final int dy = getTitleHeight();
2491        invalidate((int)Math.floor(l * scale),
2492                   (int)Math.floor(t * scale) + dy,
2493                   (int)Math.ceil(r * scale),
2494                   (int)Math.ceil(b * scale) + dy);
2495    }
2496
2497    // Called by JNI to invalidate the View after a delay, given rectangle
2498    // coordinates in content space
2499    private void viewInvalidateDelayed(long delay, int l, int t, int r, int b) {
2500        final float scale = mZoomManager.getScale();
2501        final int dy = getTitleHeight();
2502        postInvalidateDelayed(delay,
2503                              (int)Math.floor(l * scale),
2504                              (int)Math.floor(t * scale) + dy,
2505                              (int)Math.ceil(r * scale),
2506                              (int)Math.ceil(b * scale) + dy);
2507    }
2508
2509    private void invalidateContentRect(Rect r) {
2510        viewInvalidate(r.left, r.top, r.right, r.bottom);
2511    }
2512
2513    // stop the scroll animation, and don't let a subsequent fling add
2514    // to the existing velocity
2515    private void abortAnimation() {
2516        mScroller.abortAnimation();
2517        mLastVelocity = 0;
2518    }
2519
2520    /* call from webcoreview.draw(), so we're still executing in the UI thread
2521    */
2522    private void recordNewContentSize(int w, int h, boolean updateLayout) {
2523
2524        // premature data from webkit, ignore
2525        if ((w | h) == 0) {
2526            return;
2527        }
2528
2529        // don't abort a scroll animation if we didn't change anything
2530        if (mContentWidth != w || mContentHeight != h) {
2531            // record new dimensions
2532            mContentWidth = w;
2533            mContentHeight = h;
2534            // If history Picture is drawn, don't update scroll. They will be
2535            // updated when we get out of that mode.
2536            if (!mDrawHistory) {
2537                // repin our scroll, taking into account the new content size
2538                updateScrollCoordinates(pinLocX(mScrollX), pinLocY(mScrollY));
2539                if (!mScroller.isFinished()) {
2540                    // We are in the middle of a scroll.  Repin the final scroll
2541                    // position.
2542                    mScroller.setFinalX(pinLocX(mScroller.getFinalX()));
2543                    mScroller.setFinalY(pinLocY(mScroller.getFinalY()));
2544                }
2545            }
2546        }
2547        contentSizeChanged(updateLayout);
2548    }
2549
2550    // Used to avoid sending many visible rect messages.
2551    private Rect mLastVisibleRectSent;
2552    private Rect mLastGlobalRect;
2553
2554    Rect sendOurVisibleRect() {
2555        if (mZoomManager.isPreventingWebkitUpdates()) return mLastVisibleRectSent;
2556        Rect rect = new Rect();
2557        calcOurContentVisibleRect(rect);
2558        // Rect.equals() checks for null input.
2559        if (!rect.equals(mLastVisibleRectSent)) {
2560            Point pos = new Point(rect.left, rect.top);
2561            mWebViewCore.removeMessages(EventHub.SET_SCROLL_OFFSET);
2562            mWebViewCore.sendMessage(EventHub.SET_SCROLL_OFFSET,
2563                    nativeMoveGeneration(), mSendScrollEvent ? 1 : 0, pos);
2564            mLastVisibleRectSent = rect;
2565            mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
2566        }
2567        Rect globalRect = new Rect();
2568        if (getGlobalVisibleRect(globalRect)
2569                && !globalRect.equals(mLastGlobalRect)) {
2570            if (DebugFlags.WEB_VIEW) {
2571                Log.v(LOGTAG, "sendOurVisibleRect=(" + globalRect.left + ","
2572                        + globalRect.top + ",r=" + globalRect.right + ",b="
2573                        + globalRect.bottom);
2574            }
2575            // TODO: the global offset is only used by windowRect()
2576            // in ChromeClientAndroid ; other clients such as touch
2577            // and mouse events could return view + screen relative points.
2578            mWebViewCore.sendMessage(EventHub.SET_GLOBAL_BOUNDS, globalRect);
2579            mLastGlobalRect = globalRect;
2580        }
2581        return rect;
2582    }
2583
2584    // Sets r to be the visible rectangle of our webview in view coordinates
2585    private void calcOurVisibleRect(Rect r) {
2586        Point p = new Point();
2587        getGlobalVisibleRect(r, p);
2588        r.offset(-p.x, -p.y);
2589    }
2590
2591    // Sets r to be our visible rectangle in content coordinates
2592    private void calcOurContentVisibleRect(Rect r) {
2593        calcOurVisibleRect(r);
2594        r.left = viewToContentX(r.left);
2595        // viewToContentY will remove the total height of the title bar.  Add
2596        // the visible height back in to account for the fact that if the title
2597        // bar is partially visible, the part of the visible rect which is
2598        // displaying our content is displaced by that amount.
2599        r.top = viewToContentY(r.top + getVisibleTitleHeight());
2600        r.right = viewToContentX(r.right);
2601        r.bottom = viewToContentY(r.bottom);
2602    }
2603
2604    // Sets r to be our visible rectangle in content coordinates. We use this
2605    // method on the native side to compute the position of the fixed layers.
2606    // Uses floating coordinates (necessary to correctly place elements when
2607    // the scale factor is not 1)
2608    private void calcOurContentVisibleRectF(RectF r) {
2609        Rect ri = new Rect(0,0,0,0);
2610        calcOurVisibleRect(ri);
2611        r.left = viewToContentXf(ri.left);
2612        // viewToContentY will remove the total height of the title bar.  Add
2613        // the visible height back in to account for the fact that if the title
2614        // bar is partially visible, the part of the visible rect which is
2615        // displaying our content is displaced by that amount.
2616        r.top = viewToContentYf(ri.top + getVisibleTitleHeight());
2617        r.right = viewToContentXf(ri.right);
2618        r.bottom = viewToContentYf(ri.bottom);
2619    }
2620
2621    static class ViewSizeData {
2622        int mWidth;
2623        int mHeight;
2624        float mHeightWidthRatio;
2625        int mActualViewHeight;
2626        int mTextWrapWidth;
2627        int mAnchorX;
2628        int mAnchorY;
2629        float mScale;
2630        boolean mIgnoreHeight;
2631    }
2632
2633    /**
2634     * Compute unzoomed width and height, and if they differ from the last
2635     * values we sent, send them to webkit (to be used as new viewport)
2636     *
2637     * @param force ensures that the message is sent to webkit even if the width
2638     * or height has not changed since the last message
2639     *
2640     * @return true if new values were sent
2641     */
2642    boolean sendViewSizeZoom(boolean force) {
2643        if (mZoomManager.isPreventingWebkitUpdates()) return false;
2644
2645        int viewWidth = getViewWidth();
2646        int newWidth = Math.round(viewWidth * mZoomManager.getInvScale());
2647        // This height could be fixed and be different from actual visible height.
2648        int viewHeight = getViewHeightWithTitle() - getTitleHeight();
2649        int newHeight = Math.round(viewHeight * mZoomManager.getInvScale());
2650        // Make the ratio more accurate than (newHeight / newWidth), since the
2651        // latter both are calculated and rounded.
2652        float heightWidthRatio = (float) viewHeight / viewWidth;
2653        /*
2654         * Because the native side may have already done a layout before the
2655         * View system was able to measure us, we have to send a height of 0 to
2656         * remove excess whitespace when we grow our width. This will trigger a
2657         * layout and a change in content size. This content size change will
2658         * mean that contentSizeChanged will either call this method directly or
2659         * indirectly from onSizeChanged.
2660         */
2661        if (newWidth > mLastWidthSent && mWrapContent) {
2662            newHeight = 0;
2663            heightWidthRatio = 0;
2664        }
2665        // Actual visible content height.
2666        int actualViewHeight = Math.round(getViewHeight() * mZoomManager.getInvScale());
2667        // Avoid sending another message if the dimensions have not changed.
2668        if (newWidth != mLastWidthSent || newHeight != mLastHeightSent || force ||
2669                actualViewHeight != mLastActualHeightSent) {
2670            ViewSizeData data = new ViewSizeData();
2671            data.mWidth = newWidth;
2672            data.mHeight = newHeight;
2673            data.mHeightWidthRatio = heightWidthRatio;
2674            data.mActualViewHeight = actualViewHeight;
2675            data.mTextWrapWidth = Math.round(viewWidth / mZoomManager.getTextWrapScale());
2676            data.mScale = mZoomManager.getScale();
2677            data.mIgnoreHeight = mZoomManager.isFixedLengthAnimationInProgress()
2678                    && !mHeightCanMeasure;
2679            data.mAnchorX = mZoomManager.getDocumentAnchorX();
2680            data.mAnchorY = mZoomManager.getDocumentAnchorY();
2681            mWebViewCore.sendMessage(EventHub.VIEW_SIZE_CHANGED, data);
2682            mLastWidthSent = newWidth;
2683            mLastHeightSent = newHeight;
2684            mLastActualHeightSent = actualViewHeight;
2685            mZoomManager.clearDocumentAnchor();
2686            return true;
2687        }
2688        return false;
2689    }
2690
2691    private int computeRealHorizontalScrollRange() {
2692        if (mDrawHistory) {
2693            return mHistoryWidth;
2694        } else {
2695            // to avoid rounding error caused unnecessary scrollbar, use floor
2696            return (int) Math.floor(mContentWidth * mZoomManager.getScale());
2697        }
2698    }
2699
2700    @Override
2701    protected int computeHorizontalScrollRange() {
2702        int range = computeRealHorizontalScrollRange();
2703
2704        // Adjust reported range if overscrolled to compress the scroll bars
2705        final int scrollX = mScrollX;
2706        final int overscrollRight = computeMaxScrollX();
2707        if (scrollX < 0) {
2708            range -= scrollX;
2709        } else if (scrollX > overscrollRight) {
2710            range += scrollX - overscrollRight;
2711        }
2712
2713        return range;
2714    }
2715
2716    @Override
2717    protected int computeHorizontalScrollOffset() {
2718        return Math.max(mScrollX, 0);
2719    }
2720
2721    private int computeRealVerticalScrollRange() {
2722        if (mDrawHistory) {
2723            return mHistoryHeight;
2724        } else {
2725            // to avoid rounding error caused unnecessary scrollbar, use floor
2726            return (int) Math.floor(mContentHeight * mZoomManager.getScale());
2727        }
2728    }
2729
2730    @Override
2731    protected int computeVerticalScrollRange() {
2732        int range = computeRealVerticalScrollRange();
2733
2734        // Adjust reported range if overscrolled to compress the scroll bars
2735        final int scrollY = mScrollY;
2736        final int overscrollBottom = computeMaxScrollY();
2737        if (scrollY < 0) {
2738            range -= scrollY;
2739        } else if (scrollY > overscrollBottom) {
2740            range += scrollY - overscrollBottom;
2741        }
2742
2743        return range;
2744    }
2745
2746    @Override
2747    protected int computeVerticalScrollOffset() {
2748        return Math.max(mScrollY - getTitleHeight(), 0);
2749    }
2750
2751    @Override
2752    protected int computeVerticalScrollExtent() {
2753        return getViewHeight();
2754    }
2755
2756    /** @hide */
2757    @Override
2758    protected void onDrawVerticalScrollBar(Canvas canvas,
2759                                           Drawable scrollBar,
2760                                           int l, int t, int r, int b) {
2761        if (mScrollY < 0) {
2762            t -= mScrollY;
2763        }
2764        scrollBar.setBounds(l, t + getVisibleTitleHeight(), r, b);
2765        scrollBar.draw(canvas);
2766    }
2767
2768    @Override
2769    protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX,
2770            boolean clampedY) {
2771        // Special-case layer scrolling so that we do not trigger normal scroll
2772        // updating.
2773        if (mTouchMode == TOUCH_DRAG_LAYER_MODE) {
2774            nativeScrollLayer(mScrollingLayer, scrollX, scrollY);
2775            mScrollingLayerRect.left = scrollX;
2776            mScrollingLayerRect.top = scrollY;
2777            invalidate();
2778            return;
2779        }
2780        mInOverScrollMode = false;
2781        int maxX = computeMaxScrollX();
2782        int maxY = computeMaxScrollY();
2783        if (maxX == 0) {
2784            // do not over scroll x if the page just fits the screen
2785            scrollX = pinLocX(scrollX);
2786        } else if (scrollX < 0 || scrollX > maxX) {
2787            mInOverScrollMode = true;
2788        }
2789        if (scrollY < 0 || scrollY > maxY) {
2790            mInOverScrollMode = true;
2791        }
2792
2793        int oldX = mScrollX;
2794        int oldY = mScrollY;
2795
2796        super.scrollTo(scrollX, scrollY);
2797
2798        if (mOverScrollGlow != null) {
2799            mOverScrollGlow.pullGlow(mScrollX, mScrollY, oldX, oldY, maxX, maxY);
2800        }
2801    }
2802
2803    /**
2804     * Get the url for the current page. This is not always the same as the url
2805     * passed to WebViewClient.onPageStarted because although the load for
2806     * that url has begun, the current page may not have changed.
2807     * @return The url for the current page.
2808     */
2809    public String getUrl() {
2810        WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
2811        return h != null ? h.getUrl() : null;
2812    }
2813
2814    /**
2815     * Get the original url for the current page. This is not always the same
2816     * as the url passed to WebViewClient.onPageStarted because although the
2817     * load for that url has begun, the current page may not have changed.
2818     * Also, there may have been redirects resulting in a different url to that
2819     * originally requested.
2820     * @return The url that was originally requested for the current page.
2821     */
2822    public String getOriginalUrl() {
2823        WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
2824        return h != null ? h.getOriginalUrl() : null;
2825    }
2826
2827    /**
2828     * Get the title for the current page. This is the title of the current page
2829     * until WebViewClient.onReceivedTitle is called.
2830     * @return The title for the current page.
2831     */
2832    public String getTitle() {
2833        WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
2834        return h != null ? h.getTitle() : null;
2835    }
2836
2837    /**
2838     * Get the favicon for the current page. This is the favicon of the current
2839     * page until WebViewClient.onReceivedIcon is called.
2840     * @return The favicon for the current page.
2841     */
2842    public Bitmap getFavicon() {
2843        WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
2844        return h != null ? h.getFavicon() : null;
2845    }
2846
2847    /**
2848     * Get the touch icon url for the apple-touch-icon <link> element, or
2849     * a URL on this site's server pointing to the standard location of a
2850     * touch icon.
2851     * @hide
2852     */
2853    public String getTouchIconUrl() {
2854        WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
2855        return h != null ? h.getTouchIconUrl() : null;
2856    }
2857
2858    /**
2859     * Get the progress for the current page.
2860     * @return The progress for the current page between 0 and 100.
2861     */
2862    public int getProgress() {
2863        return mCallbackProxy.getProgress();
2864    }
2865
2866    /**
2867     * @return the height of the HTML content.
2868     */
2869    public int getContentHeight() {
2870        return mContentHeight;
2871    }
2872
2873    /**
2874     * @return the width of the HTML content.
2875     * @hide
2876     */
2877    public int getContentWidth() {
2878        return mContentWidth;
2879    }
2880
2881    /**
2882     * Pause all layout, parsing, and JavaScript timers for all webviews. This
2883     * is a global requests, not restricted to just this webview. This can be
2884     * useful if the application has been paused.
2885     */
2886    public void pauseTimers() {
2887        mWebViewCore.sendMessage(EventHub.PAUSE_TIMERS);
2888    }
2889
2890    /**
2891     * Resume all layout, parsing, and JavaScript timers for all webviews.
2892     * This will resume dispatching all timers.
2893     */
2894    public void resumeTimers() {
2895        mWebViewCore.sendMessage(EventHub.RESUME_TIMERS);
2896    }
2897
2898    /**
2899     * Call this to pause any extra processing associated with this WebView and
2900     * its associated DOM, plugins, JavaScript etc. For example, if the WebView
2901     * is taken offscreen, this could be called to reduce unnecessary CPU or
2902     * network traffic. When the WebView is again "active", call onResume().
2903     *
2904     * Note that this differs from pauseTimers(), which affects all WebViews.
2905     */
2906    public void onPause() {
2907        if (!mIsPaused) {
2908            mIsPaused = true;
2909            mWebViewCore.sendMessage(EventHub.ON_PAUSE);
2910            // We want to pause the current playing video when switching out
2911            // from the current WebView/tab.
2912            if (mHTML5VideoViewProxy != null) {
2913                mHTML5VideoViewProxy.pauseAndDispatch();
2914            }
2915        }
2916    }
2917
2918    /**
2919     * Call this to resume a WebView after a previous call to onPause().
2920     */
2921    public void onResume() {
2922        if (mIsPaused) {
2923            mIsPaused = false;
2924            mWebViewCore.sendMessage(EventHub.ON_RESUME);
2925        }
2926    }
2927
2928    /**
2929     * Returns true if the view is paused, meaning onPause() was called. Calling
2930     * onResume() sets the paused state back to false.
2931     * @hide
2932     */
2933    public boolean isPaused() {
2934        return mIsPaused;
2935    }
2936
2937    /**
2938     * Call this to inform the view that memory is low so that it can
2939     * free any available memory.
2940     */
2941    public void freeMemory() {
2942        mWebViewCore.sendMessage(EventHub.FREE_MEMORY);
2943    }
2944
2945    /**
2946     * Clear the resource cache. Note that the cache is per-application, so
2947     * this will clear the cache for all WebViews used.
2948     *
2949     * @param includeDiskFiles If false, only the RAM cache is cleared.
2950     */
2951    public void clearCache(boolean includeDiskFiles) {
2952        // Note: this really needs to be a static method as it clears cache for all
2953        // WebView. But we need mWebViewCore to send message to WebCore thread, so
2954        // we can't make this static.
2955        mWebViewCore.sendMessage(EventHub.CLEAR_CACHE,
2956                includeDiskFiles ? 1 : 0, 0);
2957    }
2958
2959    /**
2960     * Make sure that clearing the form data removes the adapter from the
2961     * currently focused textfield if there is one.
2962     */
2963    public void clearFormData() {
2964        if (inEditingMode()) {
2965            AutoCompleteAdapter adapter = null;
2966            mWebTextView.setAdapterCustom(adapter);
2967        }
2968    }
2969
2970    /**
2971     * Tell the WebView to clear its internal back/forward list.
2972     */
2973    public void clearHistory() {
2974        mCallbackProxy.getBackForwardList().setClearPending();
2975        mWebViewCore.sendMessage(EventHub.CLEAR_HISTORY);
2976    }
2977
2978    /**
2979     * Clear the SSL preferences table stored in response to proceeding with SSL
2980     * certificate errors.
2981     */
2982    public void clearSslPreferences() {
2983        mWebViewCore.sendMessage(EventHub.CLEAR_SSL_PREF_TABLE);
2984    }
2985
2986    /**
2987     * Return the WebBackForwardList for this WebView. This contains the
2988     * back/forward list for use in querying each item in the history stack.
2989     * This is a copy of the private WebBackForwardList so it contains only a
2990     * snapshot of the current state. Multiple calls to this method may return
2991     * different objects. The object returned from this method will not be
2992     * updated to reflect any new state.
2993     */
2994    public WebBackForwardList copyBackForwardList() {
2995        return mCallbackProxy.getBackForwardList().clone();
2996    }
2997
2998    /*
2999     * Highlight and scroll to the next occurance of String in findAll.
3000     * Wraps the page infinitely, and scrolls.  Must be called after
3001     * calling findAll.
3002     *
3003     * @param forward Direction to search.
3004     */
3005    public void findNext(boolean forward) {
3006        if (0 == mNativeClass) return; // client isn't initialized
3007        nativeFindNext(forward);
3008    }
3009
3010    /*
3011     * Find all instances of find on the page and highlight them.
3012     * @param find  String to find.
3013     * @return int  The number of occurances of the String "find"
3014     *              that were found.
3015     */
3016    public int findAll(String find) {
3017        if (0 == mNativeClass) return 0; // client isn't initialized
3018        int result = find != null ? nativeFindAll(find.toLowerCase(),
3019                find.toUpperCase(), find.equalsIgnoreCase(mLastFind)) : 0;
3020        invalidate();
3021        mLastFind = find;
3022        return result;
3023    }
3024
3025    /**
3026     * Start an ActionMode for finding text in this WebView.  Only works if this
3027     *              WebView is attached to the view system.
3028     * @param text If non-null, will be the initial text to search for.
3029     *             Otherwise, the last String searched for in this WebView will
3030     *             be used to start.
3031     * @param showIme If true, show the IME, assuming the user will begin typing.
3032     *             If false and text is non-null, perform a find all.
3033     * @return boolean True if the find dialog is shown, false otherwise.
3034     */
3035    public boolean showFindDialog(String text, boolean showIme) {
3036        FindActionModeCallback callback = new FindActionModeCallback(mContext);
3037        if (getParent() == null || startActionMode(callback) == null) {
3038            // Could not start the action mode, so end Find on page
3039            return false;
3040        }
3041        mFindCallback = callback;
3042        setFindIsUp(true);
3043        mFindCallback.setWebView(this);
3044        if (showIme) {
3045            mFindCallback.showSoftInput();
3046        } else if (text != null) {
3047            mFindCallback.setText(text);
3048            mFindCallback.findAll();
3049            return true;
3050        }
3051        if (text == null) {
3052            text = mLastFind;
3053        }
3054        if (text != null) {
3055            mFindCallback.setText(text);
3056        }
3057        return true;
3058    }
3059
3060    /**
3061     * Keep track of the find callback so that we can remove its titlebar if
3062     * necessary.
3063     */
3064    private FindActionModeCallback mFindCallback;
3065
3066    /**
3067     * Toggle whether the find dialog is showing, for both native and Java.
3068     */
3069    private void setFindIsUp(boolean isUp) {
3070        mFindIsUp = isUp;
3071        if (0 == mNativeClass) return; // client isn't initialized
3072        nativeSetFindIsUp(isUp);
3073    }
3074
3075    /**
3076     * Return the index of the currently highlighted match.
3077     */
3078    int findIndex() {
3079        if (0 == mNativeClass) return -1;
3080        return nativeFindIndex();
3081    }
3082
3083    // Used to know whether the find dialog is open.  Affects whether
3084    // or not we draw the highlights for matches.
3085    private boolean mFindIsUp;
3086
3087    // Keep track of the last string sent, so we can search again when find is
3088    // reopened.
3089    private String mLastFind;
3090
3091    /**
3092     * Return the first substring consisting of the address of a physical
3093     * location. Currently, only addresses in the United States are detected,
3094     * and consist of:
3095     * - a house number
3096     * - a street name
3097     * - a street type (Road, Circle, etc), either spelled out or abbreviated
3098     * - a city name
3099     * - a state or territory, either spelled out or two-letter abbr.
3100     * - an optional 5 digit or 9 digit zip code.
3101     *
3102     * All names must be correctly capitalized, and the zip code, if present,
3103     * must be valid for the state. The street type must be a standard USPS
3104     * spelling or abbreviation. The state or territory must also be spelled
3105     * or abbreviated using USPS standards. The house number may not exceed
3106     * five digits.
3107     * @param addr The string to search for addresses.
3108     *
3109     * @return the address, or if no address is found, return null.
3110     */
3111    public static String findAddress(String addr) {
3112        return findAddress(addr, false);
3113    }
3114
3115    /**
3116     * @hide
3117     * Return the first substring consisting of the address of a physical
3118     * location. Currently, only addresses in the United States are detected,
3119     * and consist of:
3120     * - a house number
3121     * - a street name
3122     * - a street type (Road, Circle, etc), either spelled out or abbreviated
3123     * - a city name
3124     * - a state or territory, either spelled out or two-letter abbr.
3125     * - an optional 5 digit or 9 digit zip code.
3126     *
3127     * Names are optionally capitalized, and the zip code, if present,
3128     * must be valid for the state. The street type must be a standard USPS
3129     * spelling or abbreviation. The state or territory must also be spelled
3130     * or abbreviated using USPS standards. The house number may not exceed
3131     * five digits.
3132     * @param addr The string to search for addresses.
3133     * @param caseInsensitive addr Set to true to make search ignore case.
3134     *
3135     * @return the address, or if no address is found, return null.
3136     */
3137    public static String findAddress(String addr, boolean caseInsensitive) {
3138        return WebViewCore.nativeFindAddress(addr, caseInsensitive);
3139    }
3140
3141    /*
3142     * Clear the highlighting surrounding text matches created by findAll.
3143     */
3144    public void clearMatches() {
3145        if (mNativeClass == 0)
3146            return;
3147        nativeSetFindIsEmpty();
3148        invalidate();
3149    }
3150
3151    /**
3152     * Called when the find ActionMode ends.
3153     */
3154    void notifyFindDialogDismissed() {
3155        mFindCallback = null;
3156        if (mWebViewCore == null) {
3157            return;
3158        }
3159        clearMatches();
3160        setFindIsUp(false);
3161        // Now that the dialog has been removed, ensure that we scroll to a
3162        // location that is not beyond the end of the page.
3163        pinScrollTo(mScrollX, mScrollY, false, 0);
3164        invalidate();
3165    }
3166
3167    /**
3168     * Query the document to see if it contains any image references. The
3169     * message object will be dispatched with arg1 being set to 1 if images
3170     * were found and 0 if the document does not reference any images.
3171     * @param response The message that will be dispatched with the result.
3172     */
3173    public void documentHasImages(Message response) {
3174        if (response == null) {
3175            return;
3176        }
3177        mWebViewCore.sendMessage(EventHub.DOC_HAS_IMAGES, response);
3178    }
3179
3180    /**
3181     * Request the scroller to abort any ongoing animation
3182     *
3183     * @hide
3184     */
3185    public void stopScroll() {
3186        mScroller.forceFinished(true);
3187        mLastVelocity = 0;
3188    }
3189
3190    @Override
3191    public void computeScroll() {
3192        if (mScroller.computeScrollOffset()) {
3193            int oldX = mScrollX;
3194            int oldY = mScrollY;
3195            int x = mScroller.getCurrX();
3196            int y = mScroller.getCurrY();
3197            invalidate();  // So we draw again
3198
3199            if (!mScroller.isFinished()) {
3200                int rangeX = computeMaxScrollX();
3201                int rangeY = computeMaxScrollY();
3202                int overflingDistance = mOverflingDistance;
3203
3204                // Use the layer's scroll data if needed.
3205                if (mTouchMode == TOUCH_DRAG_LAYER_MODE) {
3206                    oldX = mScrollingLayerRect.left;
3207                    oldY = mScrollingLayerRect.top;
3208                    rangeX = mScrollingLayerRect.right;
3209                    rangeY = mScrollingLayerRect.bottom;
3210                    // No overscrolling for layers.
3211                    overflingDistance = 0;
3212                }
3213
3214                overScrollBy(x - oldX, y - oldY, oldX, oldY,
3215                        rangeX, rangeY,
3216                        overflingDistance, overflingDistance, false);
3217
3218                if (mOverScrollGlow != null) {
3219                    mOverScrollGlow.absorbGlow(x, y, oldX, oldY, rangeX, rangeY);
3220                }
3221            } else {
3222                if (mTouchMode != TOUCH_DRAG_LAYER_MODE) {
3223                    mScrollX = x;
3224                    mScrollY = y;
3225                } else {
3226                    // Update the layer position instead of WebView.
3227                    nativeScrollLayer(mScrollingLayer, x, y);
3228                    mScrollingLayerRect.left = x;
3229                    mScrollingLayerRect.top = y;
3230                }
3231                abortAnimation();
3232                mPrivateHandler.removeMessages(RESUME_WEBCORE_PRIORITY);
3233                WebViewCore.resumePriority();
3234                if (!mSelectingText) {
3235                    WebViewCore.resumeUpdatePicture(mWebViewCore);
3236                }
3237                if (oldX != mScrollX || oldY != mScrollY) {
3238                    sendOurVisibleRect();
3239                }
3240            }
3241        } else {
3242            super.computeScroll();
3243        }
3244    }
3245
3246    private static int computeDuration(int dx, int dy) {
3247        int distance = Math.max(Math.abs(dx), Math.abs(dy));
3248        int duration = distance * 1000 / STD_SPEED;
3249        return Math.min(duration, MAX_DURATION);
3250    }
3251
3252    // helper to pin the scrollBy parameters (already in view coordinates)
3253    // returns true if the scroll was changed
3254    private boolean pinScrollBy(int dx, int dy, boolean animate, int animationDuration) {
3255        return pinScrollTo(mScrollX + dx, mScrollY + dy, animate, animationDuration);
3256    }
3257    // helper to pin the scrollTo parameters (already in view coordinates)
3258    // returns true if the scroll was changed
3259    private boolean pinScrollTo(int x, int y, boolean animate, int animationDuration) {
3260        x = pinLocX(x);
3261        y = pinLocY(y);
3262        int dx = x - mScrollX;
3263        int dy = y - mScrollY;
3264
3265        if ((dx | dy) == 0) {
3266            return false;
3267        }
3268        abortAnimation();
3269        if (animate) {
3270            //        Log.d(LOGTAG, "startScroll: " + dx + " " + dy);
3271            mScroller.startScroll(mScrollX, mScrollY, dx, dy,
3272                    animationDuration > 0 ? animationDuration : computeDuration(dx, dy));
3273            awakenScrollBars(mScroller.getDuration());
3274            invalidate();
3275        } else {
3276            scrollTo(x, y);
3277        }
3278        return true;
3279    }
3280
3281    // Scale from content to view coordinates, and pin.
3282    // Also called by jni webview.cpp
3283    private boolean setContentScrollBy(int cx, int cy, boolean animate) {
3284        if (mDrawHistory) {
3285            // disallow WebView to change the scroll position as History Picture
3286            // is used in the view system.
3287            // TODO: as we switchOutDrawHistory when trackball or navigation
3288            // keys are hit, this should be safe. Right?
3289            return false;
3290        }
3291        cx = contentToViewDimension(cx);
3292        cy = contentToViewDimension(cy);
3293        if (mHeightCanMeasure) {
3294            // move our visible rect according to scroll request
3295            if (cy != 0) {
3296                Rect tempRect = new Rect();
3297                calcOurVisibleRect(tempRect);
3298                tempRect.offset(cx, cy);
3299                requestRectangleOnScreen(tempRect);
3300            }
3301            // FIXME: We scroll horizontally no matter what because currently
3302            // ScrollView and ListView will not scroll horizontally.
3303            // FIXME: Why do we only scroll horizontally if there is no
3304            // vertical scroll?
3305//                Log.d(LOGTAG, "setContentScrollBy cy=" + cy);
3306            return cy == 0 && cx != 0 && pinScrollBy(cx, 0, animate, 0);
3307        } else {
3308            return pinScrollBy(cx, cy, animate, 0);
3309        }
3310    }
3311
3312    /**
3313     * Called by CallbackProxy when the page starts loading.
3314     * @param url The URL of the page which has started loading.
3315     */
3316    /* package */ void onPageStarted(String url) {
3317        // every time we start a new page, we want to reset the
3318        // WebView certificate:  if the new site is secure, we
3319        // will reload it and get a new certificate set;
3320        // if the new site is not secure, the certificate must be
3321        // null, and that will be the case
3322        setCertificate(null);
3323
3324        // reset the flag since we set to true in if need after
3325        // loading is see onPageFinished(Url)
3326        mAccessibilityScriptInjected = false;
3327    }
3328
3329    /**
3330     * Called by CallbackProxy when the page finishes loading.
3331     * @param url The URL of the page which has finished loading.
3332     */
3333    /* package */ void onPageFinished(String url) {
3334        if (mPageThatNeedsToSlideTitleBarOffScreen != null) {
3335            // If the user is now on a different page, or has scrolled the page
3336            // past the point where the title bar is offscreen, ignore the
3337            // scroll request.
3338            if (mPageThatNeedsToSlideTitleBarOffScreen.equals(url)
3339                    && mScrollX == 0 && mScrollY == 0) {
3340                pinScrollTo(0, mYDistanceToSlideTitleOffScreen, true,
3341                        SLIDE_TITLE_DURATION);
3342            }
3343            mPageThatNeedsToSlideTitleBarOffScreen = null;
3344        }
3345        mZoomManager.onPageFinished(url);
3346        injectAccessibilityForUrl(url);
3347    }
3348
3349    /**
3350     * This method injects accessibility in the loaded document if accessibility
3351     * is enabled. If JavaScript is enabled we try to inject a URL specific script.
3352     * If no URL specific script is found or JavaScript is disabled we fallback to
3353     * the default {@link AccessibilityInjector} implementation.
3354     * </p>
3355     * If the URL has the "axs" paramter set to 1 it has already done the
3356     * script injection so we do nothing. If the parameter is set to 0
3357     * the URL opts out accessibility script injection so we fall back to
3358     * the default {@link AccessibilityInjector}.
3359     * </p>
3360     * Note: If the user has not opted-in the accessibility script injection no scripts
3361     * are injected rather the default {@link AccessibilityInjector} implementation
3362     * is used.
3363     *
3364     * @param url The URL loaded by this {@link WebView}.
3365     */
3366    private void injectAccessibilityForUrl(String url) {
3367        if (mWebViewCore == null) {
3368            return;
3369        }
3370        AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext);
3371
3372        if (!accessibilityManager.isEnabled()) {
3373            // it is possible that accessibility was turned off between reloads
3374            ensureAccessibilityScriptInjectorInstance(false);
3375            return;
3376        }
3377
3378        if (!getSettings().getJavaScriptEnabled()) {
3379            // no JS so we fallback to the basic buil-in support
3380            ensureAccessibilityScriptInjectorInstance(true);
3381            return;
3382        }
3383
3384        // check the URL "axs" parameter to choose appropriate action
3385        int axsParameterValue = getAxsUrlParameterValue(url);
3386        if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED) {
3387            boolean onDeviceScriptInjectionEnabled = (Settings.Secure.getInt(mContext
3388                    .getContentResolver(), Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, 0) == 1);
3389            if (onDeviceScriptInjectionEnabled) {
3390                ensureAccessibilityScriptInjectorInstance(false);
3391                // neither script injected nor script injection opted out => we inject
3392                loadUrl(ACCESSIBILITY_SCRIPT_CHOOSER_JAVASCRIPT);
3393                // TODO: Set this flag after successfull script injection. Maybe upon injection
3394                // the chooser should update the meta tag and we check it to declare success
3395                mAccessibilityScriptInjected = true;
3396            } else {
3397                // injection disabled so we fallback to the basic built-in support
3398                ensureAccessibilityScriptInjectorInstance(true);
3399            }
3400        } else if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT) {
3401            // injection opted out so we fallback to the basic buil-in support
3402            ensureAccessibilityScriptInjectorInstance(true);
3403        } else if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED) {
3404            ensureAccessibilityScriptInjectorInstance(false);
3405            // the URL provides accessibility but we still need to add our generic script
3406            loadUrl(ACCESSIBILITY_SCRIPT_CHOOSER_JAVASCRIPT);
3407        } else {
3408            Log.e(LOGTAG, "Unknown URL value for the \"axs\" URL parameter: " + axsParameterValue);
3409        }
3410    }
3411
3412    /**
3413     * Ensures the instance of the {@link AccessibilityInjector} to be present ot not.
3414     *
3415     * @param present True to ensure an insance, false to ensure no instance.
3416     */
3417    private void ensureAccessibilityScriptInjectorInstance(boolean present) {
3418        if (present) {
3419            if (mAccessibilityInjector == null) {
3420                mAccessibilityInjector = new AccessibilityInjector(this);
3421            }
3422        } else {
3423            mAccessibilityInjector = null;
3424        }
3425    }
3426
3427    /**
3428     * Gets the "axs" URL parameter value.
3429     *
3430     * @param url A url to fetch the paramter from.
3431     * @return The parameter value if such, -1 otherwise.
3432     */
3433    private int getAxsUrlParameterValue(String url) {
3434        if (mMatchAxsUrlParameterPattern == null) {
3435            mMatchAxsUrlParameterPattern = Pattern.compile(PATTERN_MATCH_AXS_URL_PARAMETER);
3436        }
3437        Matcher matcher = mMatchAxsUrlParameterPattern.matcher(url);
3438        if (matcher.find()) {
3439            String keyValuePair = url.substring(matcher.start(), matcher.end());
3440            return Integer.parseInt(keyValuePair.split("=")[1]);
3441        }
3442        return -1;
3443    }
3444
3445    /**
3446     * The URL of a page that sent a message to scroll the title bar off screen.
3447     *
3448     * Many mobile sites tell the page to scroll to (0,1) in order to scroll the
3449     * title bar off the screen.  Sometimes, the scroll position is set before
3450     * the page finishes loading.  Rather than scrolling while the page is still
3451     * loading, keep track of the URL and new scroll position so we can perform
3452     * the scroll once the page finishes loading.
3453     */
3454    private String mPageThatNeedsToSlideTitleBarOffScreen;
3455
3456    /**
3457     * The destination Y scroll position to be used when the page finishes
3458     * loading.  See mPageThatNeedsToSlideTitleBarOffScreen.
3459     */
3460    private int mYDistanceToSlideTitleOffScreen;
3461
3462    // scale from content to view coordinates, and pin
3463    // return true if pin caused the final x/y different than the request cx/cy,
3464    // and a future scroll may reach the request cx/cy after our size has
3465    // changed
3466    // return false if the view scroll to the exact position as it is requested,
3467    // where negative numbers are taken to mean 0
3468    private boolean setContentScrollTo(int cx, int cy) {
3469        if (mDrawHistory) {
3470            // disallow WebView to change the scroll position as History Picture
3471            // is used in the view system.
3472            // One known case where this is called is that WebCore tries to
3473            // restore the scroll position. As history Picture already uses the
3474            // saved scroll position, it is ok to skip this.
3475            return false;
3476        }
3477        int vx;
3478        int vy;
3479        if ((cx | cy) == 0) {
3480            // If the page is being scrolled to (0,0), do not add in the title
3481            // bar's height, and simply scroll to (0,0). (The only other work
3482            // in contentToView_ is to multiply, so this would not change 0.)
3483            vx = 0;
3484            vy = 0;
3485        } else {
3486            vx = contentToViewX(cx);
3487            vy = contentToViewY(cy);
3488        }
3489//        Log.d(LOGTAG, "content scrollTo [" + cx + " " + cy + "] view=[" +
3490//                      vx + " " + vy + "]");
3491        // Some mobile sites attempt to scroll the title bar off the page by
3492        // scrolling to (0,1).  If we are at the top left corner of the
3493        // page, assume this is an attempt to scroll off the title bar, and
3494        // animate the title bar off screen slowly enough that the user can see
3495        // it.
3496        if (cx == 0 && cy == 1 && mScrollX == 0 && mScrollY == 0
3497                && mTitleBar != null) {
3498            // FIXME: 100 should be defined somewhere as our max progress.
3499            if (getProgress() < 100) {
3500                // Wait to scroll the title bar off screen until the page has
3501                // finished loading.  Keep track of the URL and the destination
3502                // Y position
3503                mPageThatNeedsToSlideTitleBarOffScreen = getUrl();
3504                mYDistanceToSlideTitleOffScreen = vy;
3505            } else {
3506                pinScrollTo(vx, vy, true, SLIDE_TITLE_DURATION);
3507            }
3508            // Since we are animating, we have not yet reached the desired
3509            // scroll position.  Do not return true to request another attempt
3510            return false;
3511        }
3512        pinScrollTo(vx, vy, false, 0);
3513        // If the request was to scroll to a negative coordinate, treat it as if
3514        // it was a request to scroll to 0
3515        if ((mScrollX != vx && cx >= 0) || (mScrollY != vy && cy >= 0)) {
3516            return true;
3517        } else {
3518            return false;
3519        }
3520    }
3521
3522    // scale from content to view coordinates, and pin
3523    private void spawnContentScrollTo(int cx, int cy) {
3524        if (mDrawHistory) {
3525            // disallow WebView to change the scroll position as History Picture
3526            // is used in the view system.
3527            return;
3528        }
3529        int vx = contentToViewX(cx);
3530        int vy = contentToViewY(cy);
3531        pinScrollTo(vx, vy, true, 0);
3532    }
3533
3534    /**
3535     * These are from webkit, and are in content coordinate system (unzoomed)
3536     */
3537    private void contentSizeChanged(boolean updateLayout) {
3538        // suppress 0,0 since we usually see real dimensions soon after
3539        // this avoids drawing the prev content in a funny place. If we find a
3540        // way to consolidate these notifications, this check may become
3541        // obsolete
3542        if ((mContentWidth | mContentHeight) == 0) {
3543            return;
3544        }
3545
3546        if (mHeightCanMeasure) {
3547            if (getMeasuredHeight() != contentToViewDimension(mContentHeight)
3548                    || updateLayout) {
3549                requestLayout();
3550            }
3551        } else if (mWidthCanMeasure) {
3552            if (getMeasuredWidth() != contentToViewDimension(mContentWidth)
3553                    || updateLayout) {
3554                requestLayout();
3555            }
3556        } else {
3557            // If we don't request a layout, try to send our view size to the
3558            // native side to ensure that WebCore has the correct dimensions.
3559            sendViewSizeZoom(false);
3560        }
3561    }
3562
3563    /**
3564     * Set the WebViewClient that will receive various notifications and
3565     * requests. This will replace the current handler.
3566     * @param client An implementation of WebViewClient.
3567     */
3568    public void setWebViewClient(WebViewClient client) {
3569        mCallbackProxy.setWebViewClient(client);
3570    }
3571
3572    /**
3573     * Gets the WebViewClient
3574     * @return the current WebViewClient instance.
3575     *
3576     *@hide pending API council approval.
3577     */
3578    public WebViewClient getWebViewClient() {
3579        return mCallbackProxy.getWebViewClient();
3580    }
3581
3582    /**
3583     * Register the interface to be used when content can not be handled by
3584     * the rendering engine, and should be downloaded instead. This will replace
3585     * the current handler.
3586     * @param listener An implementation of DownloadListener.
3587     */
3588    public void setDownloadListener(DownloadListener listener) {
3589        mCallbackProxy.setDownloadListener(listener);
3590    }
3591
3592    /**
3593     * Set the chrome handler. This is an implementation of WebChromeClient for
3594     * use in handling JavaScript dialogs, favicons, titles, and the progress.
3595     * This will replace the current handler.
3596     * @param client An implementation of WebChromeClient.
3597     */
3598    public void setWebChromeClient(WebChromeClient client) {
3599        mCallbackProxy.setWebChromeClient(client);
3600    }
3601
3602    /**
3603     * Gets the chrome handler.
3604     * @return the current WebChromeClient instance.
3605     *
3606     * @hide API council approval.
3607     */
3608    public WebChromeClient getWebChromeClient() {
3609        return mCallbackProxy.getWebChromeClient();
3610    }
3611
3612    /**
3613     * Set the back/forward list client. This is an implementation of
3614     * WebBackForwardListClient for handling new items and changes in the
3615     * history index.
3616     * @param client An implementation of WebBackForwardListClient.
3617     * {@hide}
3618     */
3619    public void setWebBackForwardListClient(WebBackForwardListClient client) {
3620        mCallbackProxy.setWebBackForwardListClient(client);
3621    }
3622
3623    /**
3624     * Gets the WebBackForwardListClient.
3625     * {@hide}
3626     */
3627    public WebBackForwardListClient getWebBackForwardListClient() {
3628        return mCallbackProxy.getWebBackForwardListClient();
3629    }
3630
3631    /**
3632     * Set the Picture listener. This is an interface used to receive
3633     * notifications of a new Picture.
3634     * @param listener An implementation of WebView.PictureListener.
3635     */
3636    public void setPictureListener(PictureListener listener) {
3637        mPictureListener = listener;
3638    }
3639
3640    /**
3641     * {@hide}
3642     */
3643    /* FIXME: Debug only! Remove for SDK! */
3644    public void externalRepresentation(Message callback) {
3645        mWebViewCore.sendMessage(EventHub.REQUEST_EXT_REPRESENTATION, callback);
3646    }
3647
3648    /**
3649     * {@hide}
3650     */
3651    /* FIXME: Debug only! Remove for SDK! */
3652    public void documentAsText(Message callback) {
3653        mWebViewCore.sendMessage(EventHub.REQUEST_DOC_AS_TEXT, callback);
3654    }
3655
3656    /**
3657     * Use this function to bind an object to JavaScript so that the
3658     * methods can be accessed from JavaScript.
3659     * <p><strong>IMPORTANT:</strong>
3660     * <ul>
3661     * <li> Using addJavascriptInterface() allows JavaScript to control your
3662     * application. This can be a very useful feature or a dangerous security
3663     * issue. When the HTML in the WebView is untrustworthy (for example, part
3664     * or all of the HTML is provided by some person or process), then an
3665     * attacker could inject HTML that will execute your code and possibly any
3666     * code of the attacker's choosing.<br>
3667     * Do not use addJavascriptInterface() unless all of the HTML in this
3668     * WebView was written by you.</li>
3669     * <li> The Java object that is bound runs in another thread and not in
3670     * the thread that it was constructed in.</li>
3671     * </ul></p>
3672     * @param obj The class instance to bind to JavaScript, null instances are
3673     *            ignored.
3674     * @param interfaceName The name to used to expose the instance in
3675     *                      JavaScript.
3676     */
3677    public void addJavascriptInterface(Object obj, String interfaceName) {
3678        if (obj == null) {
3679            return;
3680        }
3681        WebViewCore.JSInterfaceData arg = new WebViewCore.JSInterfaceData();
3682        arg.mObject = obj;
3683        arg.mInterfaceName = interfaceName;
3684        mWebViewCore.sendMessage(EventHub.ADD_JS_INTERFACE, arg);
3685    }
3686
3687    /**
3688     * Removes a previously added JavaScript interface with the given name.
3689     * @param interfaceName The name of the interface to remove.
3690     */
3691    public void removeJavascriptInterface(String interfaceName) {
3692        if (mWebViewCore != null) {
3693            WebViewCore.JSInterfaceData arg = new WebViewCore.JSInterfaceData();
3694            arg.mInterfaceName = interfaceName;
3695            mWebViewCore.sendMessage(EventHub.REMOVE_JS_INTERFACE, arg);
3696        }
3697    }
3698
3699    /**
3700     * Return the WebSettings object used to control the settings for this
3701     * WebView.
3702     * @return A WebSettings object that can be used to control this WebView's
3703     *         settings.
3704     */
3705    public WebSettings getSettings() {
3706        return (mWebViewCore != null) ? mWebViewCore.getSettings() : null;
3707    }
3708
3709   /**
3710    * Return the list of currently loaded plugins.
3711    * @return The list of currently loaded plugins.
3712    *
3713    * @hide
3714    * @deprecated This was used for Gears, which has been deprecated.
3715    */
3716    @Deprecated
3717    public static synchronized PluginList getPluginList() {
3718        return new PluginList();
3719    }
3720
3721   /**
3722    * @hide
3723    * @deprecated This was used for Gears, which has been deprecated.
3724    */
3725    @Deprecated
3726    public void refreshPlugins(boolean reloadOpenPages) { }
3727
3728    //-------------------------------------------------------------------------
3729    // Override View methods
3730    //-------------------------------------------------------------------------
3731
3732    @Override
3733    protected void finalize() throws Throwable {
3734        try {
3735            destroy();
3736        } finally {
3737            super.finalize();
3738        }
3739    }
3740
3741    @Override
3742    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
3743        if (child == mTitleBar) {
3744            // When drawing the title bar, move it horizontally to always show
3745            // at the top of the WebView.
3746            mTitleBar.offsetLeftAndRight(mScrollX - mTitleBar.getLeft());
3747            int newTop = 0;
3748            if (mTitleGravity == Gravity.NO_GRAVITY) {
3749                newTop = Math.min(0, mScrollY);
3750            } else if (mTitleGravity == Gravity.TOP) {
3751                newTop = mScrollY;
3752            }
3753            mTitleBar.setBottom(newTop + mTitleBar.getHeight());
3754            mTitleBar.setTop(newTop);
3755        }
3756        return super.drawChild(canvas, child, drawingTime);
3757    }
3758
3759    private void drawContent(Canvas canvas) {
3760        // Update the buttons in the picture, so when we draw the picture
3761        // to the screen, they are in the correct state.
3762        // Tell the native side if user is a) touching the screen,
3763        // b) pressing the trackball down, or c) pressing the enter key
3764        // If the cursor is on a button, we need to draw it in the pressed
3765        // state.
3766        // If mNativeClass is 0, we should not reach here, so we do not
3767        // need to check it again.
3768        nativeRecordButtons(hasFocus() && hasWindowFocus(),
3769                            mTouchMode == TOUCH_SHORTPRESS_START_MODE
3770                            || mTrackballDown || mGotCenterDown, false);
3771        drawCoreAndCursorRing(canvas, mBackgroundColor, mDrawCursorRing);
3772    }
3773
3774    /**
3775     * Draw the background when beyond bounds
3776     * @param canvas Canvas to draw into
3777     */
3778    private void drawOverScrollBackground(Canvas canvas) {
3779        if (mOverScrollBackground == null) {
3780            mOverScrollBackground = new Paint();
3781            Bitmap bm = BitmapFactory.decodeResource(
3782                    mContext.getResources(),
3783                    com.android.internal.R.drawable.status_bar_background);
3784            mOverScrollBackground.setShader(new BitmapShader(bm,
3785                    Shader.TileMode.REPEAT, Shader.TileMode.REPEAT));
3786            mOverScrollBorder = new Paint();
3787            mOverScrollBorder.setStyle(Paint.Style.STROKE);
3788            mOverScrollBorder.setStrokeWidth(0);
3789            mOverScrollBorder.setColor(0xffbbbbbb);
3790        }
3791
3792        int top = 0;
3793        int right = computeRealHorizontalScrollRange();
3794        int bottom = top + computeRealVerticalScrollRange();
3795        // first draw the background and anchor to the top of the view
3796        canvas.save();
3797        canvas.translate(mScrollX, mScrollY);
3798        canvas.clipRect(-mScrollX, top - mScrollY, right - mScrollX, bottom
3799                - mScrollY, Region.Op.DIFFERENCE);
3800        canvas.drawPaint(mOverScrollBackground);
3801        canvas.restore();
3802        // then draw the border
3803        canvas.drawRect(-1, top - 1, right, bottom, mOverScrollBorder);
3804        // next clip the region for the content
3805        canvas.clipRect(0, top, right, bottom);
3806    }
3807
3808    @Override
3809    protected void onDraw(Canvas canvas) {
3810        // if mNativeClass is 0, the WebView has been destroyed. Do nothing.
3811        if (mNativeClass == 0) {
3812            return;
3813        }
3814
3815        // if both mContentWidth and mContentHeight are 0, it means there is no
3816        // valid Picture passed to WebView yet. This can happen when WebView
3817        // just starts. Draw the background and return.
3818        if ((mContentWidth | mContentHeight) == 0 && mHistoryPicture == null) {
3819            canvas.drawColor(mBackgroundColor);
3820            return;
3821        }
3822
3823        if (canvas.isHardwareAccelerated()) {
3824            mZoomManager.setHardwareAccelerated();
3825        }
3826
3827        int saveCount = canvas.save();
3828        if (mInOverScrollMode && !getSettings()
3829                .getUseWebViewBackgroundForOverscrollBackground()) {
3830            drawOverScrollBackground(canvas);
3831        }
3832        if (mTitleBar != null) {
3833            canvas.translate(0, getTitleHeight());
3834        }
3835        drawContent(canvas);
3836        canvas.restoreToCount(saveCount);
3837
3838        if (AUTO_REDRAW_HACK && mAutoRedraw) {
3839            invalidate();
3840        }
3841        if (inEditingMode()) {
3842            mWebTextView.onDrawSubstitute();
3843        }
3844        mWebViewCore.signalRepaintDone();
3845
3846        if (mOverScrollGlow != null && mOverScrollGlow.drawEdgeGlows(canvas)) {
3847            invalidate();
3848        }
3849
3850        // paint the highlight in the end
3851        if (!mTouchHighlightRegion.isEmpty()) {
3852            if (mTouchHightlightPaint == null) {
3853                mTouchHightlightPaint = new Paint();
3854                mTouchHightlightPaint.setColor(mHightlightColor);
3855                mTouchHightlightPaint.setAntiAlias(true);
3856                mTouchHightlightPaint.setPathEffect(new CornerPathEffect(
3857                        TOUCH_HIGHLIGHT_ARC));
3858            }
3859            canvas.drawPath(mTouchHighlightRegion.getBoundaryPath(),
3860                    mTouchHightlightPaint);
3861        }
3862        if (DEBUG_TOUCH_HIGHLIGHT) {
3863            if (getSettings().getNavDump()) {
3864                if ((mTouchHighlightX | mTouchHighlightY) != 0) {
3865                    if (mTouchCrossHairColor == null) {
3866                        mTouchCrossHairColor = new Paint();
3867                        mTouchCrossHairColor.setColor(Color.RED);
3868                    }
3869                    canvas.drawLine(mTouchHighlightX - mNavSlop,
3870                            mTouchHighlightY - mNavSlop, mTouchHighlightX
3871                                    + mNavSlop + 1, mTouchHighlightY + mNavSlop
3872                                    + 1, mTouchCrossHairColor);
3873                    canvas.drawLine(mTouchHighlightX + mNavSlop + 1,
3874                            mTouchHighlightY - mNavSlop, mTouchHighlightX
3875                                    - mNavSlop,
3876                            mTouchHighlightY + mNavSlop + 1,
3877                            mTouchCrossHairColor);
3878                }
3879            }
3880        }
3881    }
3882
3883    private void removeTouchHighlight(boolean removePendingMessage) {
3884        if (removePendingMessage) {
3885            mWebViewCore.removeMessages(EventHub.GET_TOUCH_HIGHLIGHT_RECTS);
3886        }
3887        mWebViewCore.sendMessage(EventHub.REMOVE_TOUCH_HIGHLIGHT_RECTS);
3888    }
3889
3890    @Override
3891    public void setLayoutParams(ViewGroup.LayoutParams params) {
3892        if (params.height == LayoutParams.WRAP_CONTENT) {
3893            mWrapContent = true;
3894        }
3895        super.setLayoutParams(params);
3896    }
3897
3898    @Override
3899    public boolean performLongClick() {
3900        // performLongClick() is the result of a delayed message. If we switch
3901        // to windows overview, the WebView will be temporarily removed from the
3902        // view system. In that case, do nothing.
3903        if (getParent() == null) return false;
3904
3905        // A multi-finger gesture can look like a long press; make sure we don't take
3906        // long press actions if we're scaling.
3907        final ScaleGestureDetector detector = mZoomManager.getMultiTouchGestureDetector();
3908        if (detector != null && detector.isInProgress()) {
3909            return false;
3910        }
3911
3912        if (mNativeClass != 0 && nativeCursorIsTextInput()) {
3913            // Send the click so that the textfield is in focus
3914            centerKeyPressOnTextField();
3915            rebuildWebTextView();
3916        } else {
3917            clearTextEntry();
3918        }
3919        if (inEditingMode()) {
3920            // Since we just called rebuildWebTextView, the layout is not set
3921            // properly.  Update it so it can correctly find the word to select.
3922            mWebTextView.ensureLayout();
3923            // Provide a touch down event to WebTextView, which will allow it
3924            // to store the location to use in performLongClick.
3925            AbsoluteLayout.LayoutParams params
3926                    = (AbsoluteLayout.LayoutParams) mWebTextView.getLayoutParams();
3927            MotionEvent fake = MotionEvent.obtain(mLastTouchTime,
3928                    mLastTouchTime, MotionEvent.ACTION_DOWN,
3929                    mLastTouchX - params.x + mScrollX,
3930                    mLastTouchY - params.y + mScrollY, 0);
3931            mWebTextView.dispatchTouchEvent(fake);
3932            return mWebTextView.performLongClick();
3933        }
3934        if (mSelectingText) return false; // long click does nothing on selection
3935        /* if long click brings up a context menu, the super function
3936         * returns true and we're done. Otherwise, nothing happened when
3937         * the user clicked. */
3938        if (super.performLongClick()) {
3939            return true;
3940        }
3941        /* In the case where the application hasn't already handled the long
3942         * click action, look for a word under the  click. If one is found,
3943         * animate the text selection into view.
3944         * FIXME: no animation code yet */
3945        return selectText();
3946    }
3947
3948    /**
3949     * Select the word at the last click point.
3950     *
3951     * @hide pending API council approval
3952     */
3953    public boolean selectText() {
3954        int x = viewToContentX(mLastTouchX + mScrollX);
3955        int y = viewToContentY(mLastTouchY + mScrollY);
3956        return selectText(x, y);
3957    }
3958
3959    /**
3960     * Select the word at the indicated content coordinates.
3961     */
3962    boolean selectText(int x, int y) {
3963        if (!setUpSelect(true, x, y)) {
3964            return false;
3965        }
3966        nativeSetExtendSelection();
3967        mDrawSelectionPointer = false;
3968        mSelectionStarted = true;
3969        mTouchMode = TOUCH_DRAG_MODE;
3970        return true;
3971    }
3972
3973    private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
3974
3975    @Override
3976    protected void onConfigurationChanged(Configuration newConfig) {
3977        if (mSelectingText && mOrientation != newConfig.orientation) {
3978            selectionDone();
3979        }
3980        mOrientation = newConfig.orientation;
3981    }
3982
3983    /**
3984     * Keep track of the Callback so we can end its ActionMode or remove its
3985     * titlebar.
3986     */
3987    private SelectActionModeCallback mSelectCallback;
3988
3989    // These values are possible options for didUpdateWebTextViewDimensions.
3990    private static final int FULLY_ON_SCREEN = 0;
3991    private static final int INTERSECTS_SCREEN = 1;
3992    private static final int ANYWHERE = 2;
3993
3994    /**
3995     * Check to see if the focused textfield/textarea is still on screen.  If it
3996     * is, update the the dimensions and location of WebTextView.  Otherwise,
3997     * remove the WebTextView.  Should be called when the zoom level changes.
3998     * @param intersection How to determine whether the textfield/textarea is
3999     *        still on screen.
4000     * @return boolean True if the textfield/textarea is still on screen and the
4001     *         dimensions/location of WebTextView have been updated.
4002     */
4003    private boolean didUpdateWebTextViewDimensions(int intersection) {
4004        Rect contentBounds = nativeFocusCandidateNodeBounds();
4005        Rect vBox = contentToViewRect(contentBounds);
4006        Rect visibleRect = new Rect();
4007        calcOurVisibleRect(visibleRect);
4008        // If the textfield is on screen, place the WebTextView in
4009        // its new place, accounting for our new scroll/zoom values,
4010        // and adjust its textsize.
4011        boolean onScreen;
4012        switch (intersection) {
4013            case FULLY_ON_SCREEN:
4014                onScreen = visibleRect.contains(vBox);
4015                break;
4016            case INTERSECTS_SCREEN:
4017                onScreen = Rect.intersects(visibleRect, vBox);
4018                break;
4019            case ANYWHERE:
4020                onScreen = true;
4021                break;
4022            default:
4023                throw new AssertionError(
4024                        "invalid parameter passed to didUpdateWebTextViewDimensions");
4025        }
4026        if (onScreen) {
4027            mWebTextView.setRect(vBox.left, vBox.top, vBox.width(),
4028                    vBox.height());
4029            mWebTextView.updateTextSize();
4030            updateWebTextViewPadding();
4031            return true;
4032        } else {
4033            // The textfield is now off screen.  The user probably
4034            // was not zooming to see the textfield better.  Remove
4035            // the WebTextView.  If the user types a key, and the
4036            // textfield is still in focus, we will reconstruct
4037            // the WebTextView and scroll it back on screen.
4038            mWebTextView.remove();
4039            return false;
4040        }
4041    }
4042
4043    void setBaseLayer(int layer, Region invalRegion, boolean showVisualIndicator,
4044            boolean isPictureAfterFirstLayout) {
4045        if (mNativeClass == 0)
4046            return;
4047        nativeSetBaseLayer(layer, invalRegion, showVisualIndicator,
4048                isPictureAfterFirstLayout);
4049        if (mHTML5VideoViewProxy != null) {
4050            mHTML5VideoViewProxy.setBaseLayer(layer);
4051        }
4052    }
4053
4054    private void onZoomAnimationStart() {
4055        // If it is in password mode, turn it off so it does not draw misplaced.
4056        if (inEditingMode() && nativeFocusCandidateIsPassword()) {
4057            mWebTextView.setInPassword(false);
4058        }
4059    }
4060
4061    private void onZoomAnimationEnd() {
4062        // adjust the edit text view if needed
4063        if (inEditingMode() && didUpdateWebTextViewDimensions(FULLY_ON_SCREEN)
4064                && nativeFocusCandidateIsPassword()) {
4065            // If it is a password field, start drawing the WebTextView once
4066            // again.
4067            mWebTextView.setInPassword(true);
4068        }
4069    }
4070
4071    void onFixedLengthZoomAnimationStart() {
4072        WebViewCore.pauseUpdatePicture(getWebViewCore());
4073        onZoomAnimationStart();
4074    }
4075
4076    void onFixedLengthZoomAnimationEnd() {
4077        if (!mSelectingText) {
4078            WebViewCore.resumeUpdatePicture(mWebViewCore);
4079        }
4080        onZoomAnimationEnd();
4081    }
4082
4083    private static final int ZOOM_BITS = Paint.FILTER_BITMAP_FLAG |
4084                                         Paint.DITHER_FLAG |
4085                                         Paint.SUBPIXEL_TEXT_FLAG;
4086    private static final int SCROLL_BITS = Paint.FILTER_BITMAP_FLAG |
4087                                           Paint.DITHER_FLAG;
4088
4089    private final DrawFilter mZoomFilter =
4090            new PaintFlagsDrawFilter(ZOOM_BITS, Paint.LINEAR_TEXT_FLAG);
4091    // If we need to trade better quality for speed, set mScrollFilter to null
4092    private final DrawFilter mScrollFilter =
4093            new PaintFlagsDrawFilter(SCROLL_BITS, 0);
4094
4095    private void drawCoreAndCursorRing(Canvas canvas, int color,
4096        boolean drawCursorRing) {
4097        if (mDrawHistory) {
4098            canvas.scale(mZoomManager.getScale(), mZoomManager.getScale());
4099            canvas.drawPicture(mHistoryPicture);
4100            return;
4101        }
4102        if (mNativeClass == 0) return;
4103
4104        boolean animateZoom = mZoomManager.isFixedLengthAnimationInProgress();
4105        boolean animateScroll = ((!mScroller.isFinished()
4106                || mVelocityTracker != null)
4107                && (mTouchMode != TOUCH_DRAG_MODE ||
4108                mHeldMotionless != MOTIONLESS_TRUE))
4109                || mDeferTouchMode == TOUCH_DRAG_MODE;
4110        if (mTouchMode == TOUCH_DRAG_MODE) {
4111            if (mHeldMotionless == MOTIONLESS_PENDING) {
4112                mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
4113                mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
4114                mHeldMotionless = MOTIONLESS_FALSE;
4115            }
4116            if (mHeldMotionless == MOTIONLESS_FALSE) {
4117                mPrivateHandler.sendMessageDelayed(mPrivateHandler
4118                        .obtainMessage(DRAG_HELD_MOTIONLESS), MOTIONLESS_TIME);
4119                mHeldMotionless = MOTIONLESS_PENDING;
4120            }
4121        }
4122        if (animateZoom) {
4123            mZoomManager.animateZoom(canvas);
4124        } else if (!canvas.isHardwareAccelerated()) {
4125            canvas.scale(mZoomManager.getScale(), mZoomManager.getScale());
4126        }
4127
4128        boolean UIAnimationsRunning = false;
4129        // Currently for each draw we compute the animation values;
4130        // We may in the future decide to do that independently.
4131        if (mNativeClass != 0 && nativeEvaluateLayersAnimations()) {
4132            UIAnimationsRunning = true;
4133            // If we have unfinished (or unstarted) animations,
4134            // we ask for a repaint. We only need to do this in software
4135            // rendering (with hardware rendering we already have a different
4136            // method of requesting a repaint)
4137            if (!canvas.isHardwareAccelerated())
4138                invalidate();
4139        }
4140
4141        // decide which adornments to draw
4142        int extras = DRAW_EXTRAS_NONE;
4143        if (mFindIsUp) {
4144            extras = DRAW_EXTRAS_FIND;
4145        } else if (mSelectingText) {
4146            extras = DRAW_EXTRAS_SELECTION;
4147            nativeSetSelectionPointer(mDrawSelectionPointer,
4148                    mZoomManager.getInvScale(),
4149                    mSelectX, mSelectY - getTitleHeight());
4150        } else if (drawCursorRing) {
4151            extras = DRAW_EXTRAS_CURSOR_RING;
4152        }
4153        if (DebugFlags.WEB_VIEW) {
4154            Log.v(LOGTAG, "mFindIsUp=" + mFindIsUp
4155                    + " mSelectingText=" + mSelectingText
4156                    + " nativePageShouldHandleShiftAndArrows()="
4157                    + nativePageShouldHandleShiftAndArrows()
4158                    + " animateZoom=" + animateZoom
4159                    + " extras=" + extras);
4160        }
4161
4162        if (canvas.isHardwareAccelerated()) {
4163            int functor = nativeGetDrawGLFunction(mGLViewportEmpty ? null : mGLRectViewport,
4164                    mGLViewportEmpty ? null : mViewRectViewport, getScale(), extras);
4165            ((HardwareCanvas) canvas).callDrawGLFunction(functor);
4166        } else {
4167            DrawFilter df = null;
4168            if (mZoomManager.isZoomAnimating() || UIAnimationsRunning) {
4169                df = mZoomFilter;
4170            } else if (animateScroll) {
4171                df = mScrollFilter;
4172            }
4173            canvas.setDrawFilter(df);
4174            // XXX: Revisit splitting content.  Right now it causes a
4175            // synchronization problem with layers.
4176            int content = nativeDraw(canvas, color, extras, false);
4177            canvas.setDrawFilter(null);
4178            if (content != 0) {
4179                mWebViewCore.sendMessage(EventHub.SPLIT_PICTURE_SET, content, 0);
4180            }
4181        }
4182
4183        if (extras == DRAW_EXTRAS_CURSOR_RING) {
4184            if (mTouchMode == TOUCH_SHORTPRESS_START_MODE) {
4185                mTouchMode = TOUCH_SHORTPRESS_MODE;
4186            }
4187        }
4188        if (mFocusSizeChanged) {
4189            mFocusSizeChanged = false;
4190            // If we are zooming, this will get handled above, when the zoom
4191            // finishes.  We also do not need to do this unless the WebTextView
4192            // is showing.
4193            if (!animateZoom && inEditingMode()) {
4194                didUpdateWebTextViewDimensions(ANYWHERE);
4195            }
4196        }
4197    }
4198
4199    // draw history
4200    private boolean mDrawHistory = false;
4201    private Picture mHistoryPicture = null;
4202    private int mHistoryWidth = 0;
4203    private int mHistoryHeight = 0;
4204
4205    // Only check the flag, can be called from WebCore thread
4206    boolean drawHistory() {
4207        return mDrawHistory;
4208    }
4209
4210    int getHistoryPictureWidth() {
4211        return (mHistoryPicture != null) ? mHistoryPicture.getWidth() : 0;
4212    }
4213
4214    // Should only be called in UI thread
4215    void switchOutDrawHistory() {
4216        if (null == mWebViewCore) return; // CallbackProxy may trigger this
4217        if (mDrawHistory && (getProgress() == 100 || nativeHasContent())) {
4218            mDrawHistory = false;
4219            mHistoryPicture = null;
4220            invalidate();
4221            int oldScrollX = mScrollX;
4222            int oldScrollY = mScrollY;
4223            mScrollX = pinLocX(mScrollX);
4224            mScrollY = pinLocY(mScrollY);
4225            if (oldScrollX != mScrollX || oldScrollY != mScrollY) {
4226                onScrollChanged(mScrollX, mScrollY, oldScrollX, oldScrollY);
4227            } else {
4228                sendOurVisibleRect();
4229            }
4230        }
4231    }
4232
4233    WebViewCore.CursorData cursorData() {
4234        WebViewCore.CursorData result = new WebViewCore.CursorData();
4235        result.mMoveGeneration = nativeMoveGeneration();
4236        result.mFrame = nativeCursorFramePointer();
4237        Point position = nativeCursorPosition();
4238        result.mX = position.x;
4239        result.mY = position.y;
4240        return result;
4241    }
4242
4243    /**
4244     *  Delete text from start to end in the focused textfield. If there is no
4245     *  focus, or if start == end, silently fail.  If start and end are out of
4246     *  order, swap them.
4247     *  @param  start   Beginning of selection to delete.
4248     *  @param  end     End of selection to delete.
4249     */
4250    /* package */ void deleteSelection(int start, int end) {
4251        mTextGeneration++;
4252        WebViewCore.TextSelectionData data
4253                = new WebViewCore.TextSelectionData(start, end);
4254        mWebViewCore.sendMessage(EventHub.DELETE_SELECTION, mTextGeneration, 0,
4255                data);
4256    }
4257
4258    /**
4259     *  Set the selection to (start, end) in the focused textfield. If start and
4260     *  end are out of order, swap them.
4261     *  @param  start   Beginning of selection.
4262     *  @param  end     End of selection.
4263     */
4264    /* package */ void setSelection(int start, int end) {
4265        if (mWebViewCore != null) {
4266            mWebViewCore.sendMessage(EventHub.SET_SELECTION, start, end);
4267        }
4268    }
4269
4270    @Override
4271    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
4272      InputConnection connection = super.onCreateInputConnection(outAttrs);
4273      outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_FULLSCREEN;
4274      return connection;
4275    }
4276
4277    /**
4278     * Called in response to a message from webkit telling us that the soft
4279     * keyboard should be launched.
4280     */
4281    private void displaySoftKeyboard(boolean isTextView) {
4282        InputMethodManager imm = (InputMethodManager)
4283                getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
4284
4285        // bring it back to the default level scale so that user can enter text
4286        boolean zoom = mZoomManager.getScale() < mZoomManager.getDefaultScale();
4287        if (zoom) {
4288            mZoomManager.setZoomCenter(mLastTouchX, mLastTouchY);
4289            mZoomManager.setZoomScale(mZoomManager.getDefaultScale(), false);
4290        }
4291        if (isTextView) {
4292            rebuildWebTextView();
4293            if (inEditingMode()) {
4294                imm.showSoftInput(mWebTextView, 0, mWebTextView.getResultReceiver());
4295                if (zoom) {
4296                    didUpdateWebTextViewDimensions(INTERSECTS_SCREEN);
4297                }
4298                return;
4299            }
4300        }
4301        // Used by plugins and contentEditable.
4302        // Also used if the navigation cache is out of date, and
4303        // does not recognize that a textfield is in focus.  In that
4304        // case, use WebView as the targeted view.
4305        // see http://b/issue?id=2457459
4306        imm.showSoftInput(this, 0);
4307    }
4308
4309    // Called by WebKit to instruct the UI to hide the keyboard
4310    private void hideSoftKeyboard() {
4311        InputMethodManager imm = InputMethodManager.peekInstance();
4312        if (imm != null && (imm.isActive(this)
4313                || (inEditingMode() && imm.isActive(mWebTextView)))) {
4314            imm.hideSoftInputFromWindow(this.getWindowToken(), 0);
4315        }
4316    }
4317
4318    /*
4319     * This method checks the current focus and cursor and potentially rebuilds
4320     * mWebTextView to have the appropriate properties, such as password,
4321     * multiline, and what text it contains.  It also removes it if necessary.
4322     */
4323    /* package */ void rebuildWebTextView() {
4324        // If the WebView does not have focus, do nothing until it gains focus.
4325        if (!hasFocus() && (null == mWebTextView || !mWebTextView.hasFocus())) {
4326            return;
4327        }
4328        boolean alreadyThere = inEditingMode();
4329        // inEditingMode can only return true if mWebTextView is non-null,
4330        // so we can safely call remove() if (alreadyThere)
4331        if (0 == mNativeClass || !nativeFocusCandidateIsTextInput()) {
4332            if (alreadyThere) {
4333                mWebTextView.remove();
4334            }
4335            return;
4336        }
4337        // At this point, we know we have found an input field, so go ahead
4338        // and create the WebTextView if necessary.
4339        if (mWebTextView == null) {
4340            mWebTextView = new WebTextView(mContext, WebView.this, mAutoFillData.getQueryId());
4341            // Initialize our generation number.
4342            mTextGeneration = 0;
4343        }
4344        mWebTextView.updateTextSize();
4345        Rect visibleRect = new Rect();
4346        calcOurContentVisibleRect(visibleRect);
4347        // Note that sendOurVisibleRect calls viewToContent, so the coordinates
4348        // should be in content coordinates.
4349        Rect bounds = nativeFocusCandidateNodeBounds();
4350        Rect vBox = contentToViewRect(bounds);
4351        mWebTextView.setRect(vBox.left, vBox.top, vBox.width(), vBox.height());
4352        if (!Rect.intersects(bounds, visibleRect)) {
4353            revealSelection();
4354        }
4355        String text = nativeFocusCandidateText();
4356        int nodePointer = nativeFocusCandidatePointer();
4357        if (alreadyThere && mWebTextView.isSameTextField(nodePointer)) {
4358            // It is possible that we have the same textfield, but it has moved,
4359            // i.e. In the case of opening/closing the screen.
4360            // In that case, we need to set the dimensions, but not the other
4361            // aspects.
4362            // If the text has been changed by webkit, update it.  However, if
4363            // there has been more UI text input, ignore it.  We will receive
4364            // another update when that text is recognized.
4365            if (text != null && !text.equals(mWebTextView.getText().toString())
4366                    && nativeTextGeneration() == mTextGeneration) {
4367                mWebTextView.setTextAndKeepSelection(text);
4368            }
4369        } else {
4370            mWebTextView.setGravity(nativeFocusCandidateIsRtlText() ?
4371                    Gravity.RIGHT : Gravity.NO_GRAVITY);
4372            // This needs to be called before setType, which may call
4373            // requestFormData, and it needs to have the correct nodePointer.
4374            mWebTextView.setNodePointer(nodePointer);
4375            mWebTextView.setType(nativeFocusCandidateType());
4376            updateWebTextViewPadding();
4377            if (null == text) {
4378                if (DebugFlags.WEB_VIEW) {
4379                    Log.v(LOGTAG, "rebuildWebTextView null == text");
4380                }
4381                text = "";
4382            }
4383            mWebTextView.setTextAndKeepSelection(text);
4384            InputMethodManager imm = InputMethodManager.peekInstance();
4385            if (imm != null && imm.isActive(mWebTextView)) {
4386                imm.restartInput(mWebTextView);
4387            }
4388        }
4389        if (isFocused()) {
4390            mWebTextView.requestFocus();
4391        }
4392    }
4393
4394    /**
4395     * Update the padding of mWebTextView based on the native textfield/textarea
4396     */
4397    void updateWebTextViewPadding() {
4398        Rect paddingRect = nativeFocusCandidatePaddingRect();
4399        if (paddingRect != null) {
4400            // Use contentToViewDimension since these are the dimensions of
4401            // the padding.
4402            mWebTextView.setPadding(
4403                    contentToViewDimension(paddingRect.left),
4404                    contentToViewDimension(paddingRect.top),
4405                    contentToViewDimension(paddingRect.right),
4406                    contentToViewDimension(paddingRect.bottom));
4407        }
4408    }
4409
4410    /**
4411     * Tell webkit to put the cursor on screen.
4412     */
4413    /* package */ void revealSelection() {
4414        if (mWebViewCore != null) {
4415            mWebViewCore.sendMessage(EventHub.REVEAL_SELECTION);
4416        }
4417    }
4418
4419    /**
4420     * Called by WebTextView to find saved form data associated with the
4421     * textfield
4422     * @param name Name of the textfield.
4423     * @param nodePointer Pointer to the node of the textfield, so it can be
4424     *          compared to the currently focused textfield when the data is
4425     *          retrieved.
4426     * @param autoFillable true if WebKit has determined this field is part of
4427     *          a form that can be auto filled.
4428     * @param autoComplete true if the attribute "autocomplete" is set to true
4429     *          on the textfield.
4430     */
4431    /* package */ void requestFormData(String name, int nodePointer,
4432            boolean autoFillable, boolean autoComplete) {
4433        if (mWebViewCore.getSettings().getSaveFormData()) {
4434            Message update = mPrivateHandler.obtainMessage(REQUEST_FORM_DATA);
4435            update.arg1 = nodePointer;
4436            RequestFormData updater = new RequestFormData(name, getUrl(),
4437                    update, autoFillable, autoComplete);
4438            Thread t = new Thread(updater);
4439            t.start();
4440        }
4441    }
4442
4443    /**
4444     * Pass a message to find out the <label> associated with the <input>
4445     * identified by nodePointer
4446     * @param framePointer Pointer to the frame containing the <input> node
4447     * @param nodePointer Pointer to the node for which a <label> is desired.
4448     */
4449    /* package */ void requestLabel(int framePointer, int nodePointer) {
4450        mWebViewCore.sendMessage(EventHub.REQUEST_LABEL, framePointer,
4451                nodePointer);
4452    }
4453
4454    /*
4455     * This class requests an Adapter for the WebTextView which shows past
4456     * entries stored in the database.  It is a Runnable so that it can be done
4457     * in its own thread, without slowing down the UI.
4458     */
4459    private class RequestFormData implements Runnable {
4460        private String mName;
4461        private String mUrl;
4462        private Message mUpdateMessage;
4463        private boolean mAutoFillable;
4464        private boolean mAutoComplete;
4465
4466        public RequestFormData(String name, String url, Message msg,
4467                boolean autoFillable, boolean autoComplete) {
4468            mName = name;
4469            mUrl = url;
4470            mUpdateMessage = msg;
4471            mAutoFillable = autoFillable;
4472            mAutoComplete = autoComplete;
4473        }
4474
4475        public void run() {
4476            ArrayList<String> pastEntries = new ArrayList<String>();
4477
4478            if (mAutoFillable) {
4479                // Note that code inside the adapter click handler in WebTextView depends
4480                // on the AutoFill item being at the top of the drop down list. If you change
4481                // the order, make sure to do it there too!
4482                WebSettings settings = getSettings();
4483                if (settings != null && settings.getAutoFillProfile() != null) {
4484                    pastEntries.add(getResources().getText(
4485                            com.android.internal.R.string.autofill_this_form).toString() +
4486                            " " +
4487                            mAutoFillData.getPreviewString());
4488                    mWebTextView.setAutoFillProfileIsSet(true);
4489                } else {
4490                    // There is no autofill profile set up yet, so add an option that
4491                    // will invite the user to set their profile up.
4492                    pastEntries.add(getResources().getText(
4493                            com.android.internal.R.string.setup_autofill).toString());
4494                    mWebTextView.setAutoFillProfileIsSet(false);
4495                }
4496            }
4497
4498            if (mAutoComplete) {
4499                pastEntries.addAll(mDatabase.getFormData(mUrl, mName));
4500            }
4501
4502            if (pastEntries.size() > 0) {
4503                AutoCompleteAdapter adapter = new
4504                        AutoCompleteAdapter(mContext, pastEntries);
4505                mUpdateMessage.obj = adapter;
4506                mUpdateMessage.sendToTarget();
4507            }
4508        }
4509    }
4510
4511    /**
4512     * Dump the display tree to "/sdcard/displayTree.txt"
4513     *
4514     * @hide debug only
4515     */
4516    public void dumpDisplayTree() {
4517        nativeDumpDisplayTree(getUrl());
4518    }
4519
4520    /**
4521     * Dump the dom tree to adb shell if "toFile" is False, otherwise dump it to
4522     * "/sdcard/domTree.txt"
4523     *
4524     * @hide debug only
4525     */
4526    public void dumpDomTree(boolean toFile) {
4527        mWebViewCore.sendMessage(EventHub.DUMP_DOMTREE, toFile ? 1 : 0, 0);
4528    }
4529
4530    /**
4531     * Dump the render tree to adb shell if "toFile" is False, otherwise dump it
4532     * to "/sdcard/renderTree.txt"
4533     *
4534     * @hide debug only
4535     */
4536    public void dumpRenderTree(boolean toFile) {
4537        mWebViewCore.sendMessage(EventHub.DUMP_RENDERTREE, toFile ? 1 : 0, 0);
4538    }
4539
4540    /**
4541     * Called by DRT on UI thread, need to proxy to WebCore thread.
4542     *
4543     * @hide debug only
4544     */
4545    public void useMockDeviceOrientation() {
4546        mWebViewCore.sendMessage(EventHub.USE_MOCK_DEVICE_ORIENTATION);
4547    }
4548
4549    /**
4550     * Called by DRT on WebCore thread.
4551     *
4552     * @hide debug only
4553     */
4554    public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
4555            boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) {
4556        mWebViewCore.setMockDeviceOrientation(canProvideAlpha, alpha, canProvideBeta, beta,
4557                canProvideGamma, gamma);
4558    }
4559
4560    /**
4561     * Dump the V8 counters to standard output.
4562     * Note that you need a build with V8 and WEBCORE_INSTRUMENTATION set to
4563     * true. Otherwise, this will do nothing.
4564     *
4565     * @hide debug only
4566     */
4567    public void dumpV8Counters() {
4568        mWebViewCore.sendMessage(EventHub.DUMP_V8COUNTERS);
4569    }
4570
4571    // This is used to determine long press with the center key.  Does not
4572    // affect long press with the trackball/touch.
4573    private boolean mGotCenterDown = false;
4574
4575    @Override
4576    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
4577        // send complex characters to webkit for use by JS and plugins
4578        if (keyCode == KeyEvent.KEYCODE_UNKNOWN && event.getCharacters() != null) {
4579            // pass the key to DOM
4580            mWebViewCore.sendMessage(EventHub.KEY_DOWN, event);
4581            mWebViewCore.sendMessage(EventHub.KEY_UP, event);
4582            // return true as DOM handles the key
4583            return true;
4584        }
4585        return false;
4586    }
4587
4588    private boolean isEnterActionKey(int keyCode) {
4589        return keyCode == KeyEvent.KEYCODE_DPAD_CENTER
4590                || keyCode == KeyEvent.KEYCODE_ENTER
4591                || keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER;
4592    }
4593
4594    @Override
4595    public boolean onKeyDown(int keyCode, KeyEvent event) {
4596        if (DebugFlags.WEB_VIEW) {
4597            Log.v(LOGTAG, "keyDown at " + System.currentTimeMillis()
4598                    + "keyCode=" + keyCode
4599                    + ", " + event + ", unicode=" + event.getUnicodeChar());
4600        }
4601
4602        // don't implement accelerator keys here; defer to host application
4603        if (event.isCtrlPressed()) {
4604            return false;
4605        }
4606
4607        if (mNativeClass == 0) {
4608            return false;
4609        }
4610
4611        // do this hack up front, so it always works, regardless of touch-mode
4612        if (AUTO_REDRAW_HACK && (keyCode == KeyEvent.KEYCODE_CALL)) {
4613            mAutoRedraw = !mAutoRedraw;
4614            if (mAutoRedraw) {
4615                invalidate();
4616            }
4617            return true;
4618        }
4619
4620        // Bubble up the key event if
4621        // 1. it is a system key; or
4622        // 2. the host application wants to handle it;
4623        if (event.isSystem()
4624                || mCallbackProxy.uiOverrideKeyEvent(event)) {
4625            return false;
4626        }
4627
4628        // accessibility support
4629        if (accessibilityScriptInjected()) {
4630            if (AccessibilityManager.getInstance(mContext).isEnabled()) {
4631                // if an accessibility script is injected we delegate to it the key handling.
4632                // this script is a screen reader which is a fully fledged solution for blind
4633                // users to navigate in and interact with web pages.
4634                mWebViewCore.sendMessage(EventHub.KEY_DOWN, event);
4635                return true;
4636            } else {
4637                // Clean up if accessibility was disabled after loading the current URL.
4638                mAccessibilityScriptInjected = false;
4639            }
4640        } else if (mAccessibilityInjector != null) {
4641            if (AccessibilityManager.getInstance(mContext).isEnabled()) {
4642                if (mAccessibilityInjector.onKeyEvent(event)) {
4643                    // if an accessibility injector is present (no JavaScript enabled or the site
4644                    // opts out injecting our JavaScript screen reader) we let it decide whether
4645                    // to act on and consume the event.
4646                    return true;
4647                }
4648            } else {
4649                // Clean up if accessibility was disabled after loading the current URL.
4650                mAccessibilityInjector = null;
4651            }
4652        }
4653
4654        if (keyCode == KeyEvent.KEYCODE_PAGE_UP) {
4655            if (event.hasNoModifiers()) {
4656                pageUp(false);
4657                return true;
4658            } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
4659                pageUp(true);
4660                return true;
4661            }
4662        }
4663
4664        if (keyCode == KeyEvent.KEYCODE_PAGE_DOWN) {
4665            if (event.hasNoModifiers()) {
4666                pageDown(false);
4667                return true;
4668            } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
4669                pageDown(true);
4670                return true;
4671            }
4672        }
4673
4674        if (keyCode == KeyEvent.KEYCODE_MOVE_HOME && event.hasNoModifiers()) {
4675            pageUp(true);
4676            return true;
4677        }
4678
4679        if (keyCode == KeyEvent.KEYCODE_MOVE_END && event.hasNoModifiers()) {
4680            pageDown(true);
4681            return true;
4682        }
4683
4684        if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
4685                && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
4686            switchOutDrawHistory();
4687            if (nativePageShouldHandleShiftAndArrows()) {
4688                letPageHandleNavKey(keyCode, event.getEventTime(), true, event.getMetaState());
4689                return true;
4690            }
4691            if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
4692                switch (keyCode) {
4693                    case KeyEvent.KEYCODE_DPAD_UP:
4694                        pageUp(true);
4695                        return true;
4696                    case KeyEvent.KEYCODE_DPAD_DOWN:
4697                        pageDown(true);
4698                        return true;
4699                    case KeyEvent.KEYCODE_DPAD_LEFT:
4700                        nativeClearCursor(); // start next trackball movement from page edge
4701                        return pinScrollTo(0, mScrollY, true, 0);
4702                    case KeyEvent.KEYCODE_DPAD_RIGHT:
4703                        nativeClearCursor(); // start next trackball movement from page edge
4704                        return pinScrollTo(mContentWidth, mScrollY, true, 0);
4705                }
4706            }
4707            if (mSelectingText) {
4708                int xRate = keyCode == KeyEvent.KEYCODE_DPAD_LEFT
4709                    ? -1 : keyCode == KeyEvent.KEYCODE_DPAD_RIGHT ? 1 : 0;
4710                int yRate = keyCode == KeyEvent.KEYCODE_DPAD_UP ?
4711                    -1 : keyCode == KeyEvent.KEYCODE_DPAD_DOWN ? 1 : 0;
4712                int multiplier = event.getRepeatCount() + 1;
4713                moveSelection(xRate * multiplier, yRate * multiplier);
4714                return true;
4715            }
4716            if (navHandledKey(keyCode, 1, false, event.getEventTime())) {
4717                playSoundEffect(keyCodeToSoundsEffect(keyCode));
4718                return true;
4719            }
4720            // Bubble up the key event as WebView doesn't handle it
4721            return false;
4722        }
4723
4724        if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
4725            switchOutDrawHistory();
4726            boolean wantsKeyEvents = nativeCursorNodePointer() == 0
4727                || nativeCursorWantsKeyEvents();
4728            if (event.getRepeatCount() == 0) {
4729                if (mSelectingText) {
4730                    return true; // discard press if copy in progress
4731                }
4732                mGotCenterDown = true;
4733                mPrivateHandler.sendMessageDelayed(mPrivateHandler
4734                        .obtainMessage(LONG_PRESS_CENTER), LONG_PRESS_TIMEOUT);
4735                // Already checked mNativeClass, so we do not need to check it
4736                // again.
4737                nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true);
4738                if (!wantsKeyEvents) return true;
4739            }
4740            // Bubble up the key event as WebView doesn't handle it
4741            if (!wantsKeyEvents) return false;
4742        }
4743
4744        if (getSettings().getNavDump()) {
4745            switch (keyCode) {
4746                case KeyEvent.KEYCODE_4:
4747                    dumpDisplayTree();
4748                    break;
4749                case KeyEvent.KEYCODE_5:
4750                case KeyEvent.KEYCODE_6:
4751                    dumpDomTree(keyCode == KeyEvent.KEYCODE_5);
4752                    break;
4753                case KeyEvent.KEYCODE_7:
4754                case KeyEvent.KEYCODE_8:
4755                    dumpRenderTree(keyCode == KeyEvent.KEYCODE_7);
4756                    break;
4757                case KeyEvent.KEYCODE_9:
4758                    nativeInstrumentReport();
4759                    return true;
4760            }
4761        }
4762
4763        if (nativeCursorIsTextInput()) {
4764            // This message will put the node in focus, for the DOM's notion
4765            // of focus.
4766            mWebViewCore.sendMessage(EventHub.FAKE_CLICK, nativeCursorFramePointer(),
4767                    nativeCursorNodePointer());
4768            // This will bring up the WebTextView and put it in focus, for
4769            // our view system's notion of focus
4770            rebuildWebTextView();
4771            // Now we need to pass the event to it
4772            if (inEditingMode()) {
4773                mWebTextView.setDefaultSelection();
4774                return mWebTextView.dispatchKeyEvent(event);
4775            }
4776        } else if (nativeHasFocusNode()) {
4777            // In this case, the cursor is not on a text input, but the focus
4778            // might be.  Check it, and if so, hand over to the WebTextView.
4779            rebuildWebTextView();
4780            if (inEditingMode()) {
4781                mWebTextView.setDefaultSelection();
4782                return mWebTextView.dispatchKeyEvent(event);
4783            }
4784        }
4785
4786        // TODO: should we pass all the keys to DOM or check the meta tag
4787        if (nativeCursorWantsKeyEvents() || true) {
4788            // pass the key to DOM
4789            mWebViewCore.sendMessage(EventHub.KEY_DOWN, event);
4790            // return true as DOM handles the key
4791            return true;
4792        }
4793
4794        // Bubble up the key event as WebView doesn't handle it
4795        return false;
4796    }
4797
4798    @Override
4799    public boolean onKeyUp(int keyCode, KeyEvent event) {
4800        if (DebugFlags.WEB_VIEW) {
4801            Log.v(LOGTAG, "keyUp at " + System.currentTimeMillis()
4802                    + ", " + event + ", unicode=" + event.getUnicodeChar());
4803        }
4804
4805        if (mNativeClass == 0) {
4806            return false;
4807        }
4808
4809        // special CALL handling when cursor node's href is "tel:XXX"
4810        if (keyCode == KeyEvent.KEYCODE_CALL && nativeHasCursorNode()) {
4811            String text = nativeCursorText();
4812            if (!nativeCursorIsTextInput() && text != null
4813                    && text.startsWith(SCHEME_TEL)) {
4814                Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(text));
4815                getContext().startActivity(intent);
4816                return true;
4817            }
4818        }
4819
4820        // Bubble up the key event if
4821        // 1. it is a system key; or
4822        // 2. the host application wants to handle it;
4823        if (event.isSystem()
4824                || mCallbackProxy.uiOverrideKeyEvent(event)) {
4825            return false;
4826        }
4827
4828        // accessibility support
4829        if (accessibilityScriptInjected()) {
4830            if (AccessibilityManager.getInstance(mContext).isEnabled()) {
4831                // if an accessibility script is injected we delegate to it the key handling.
4832                // this script is a screen reader which is a fully fledged solution for blind
4833                // users to navigate in and interact with web pages.
4834                mWebViewCore.sendMessage(EventHub.KEY_UP, event);
4835                return true;
4836            } else {
4837                // Clean up if accessibility was disabled after loading the current URL.
4838                mAccessibilityScriptInjected = false;
4839            }
4840        } else if (mAccessibilityInjector != null) {
4841            if (AccessibilityManager.getInstance(mContext).isEnabled()) {
4842                if (mAccessibilityInjector.onKeyEvent(event)) {
4843                    // if an accessibility injector is present (no JavaScript enabled or the site
4844                    // opts out injecting our JavaScript screen reader) we let it decide whether to
4845                    // act on and consume the event.
4846                    return true;
4847                }
4848            } else {
4849                // Clean up if accessibility was disabled after loading the current URL.
4850                mAccessibilityInjector = null;
4851            }
4852        }
4853
4854        if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
4855                && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
4856            if (nativePageShouldHandleShiftAndArrows()) {
4857                letPageHandleNavKey(keyCode, event.getEventTime(), false, event.getMetaState());
4858                return true;
4859            }
4860            // always handle the navigation keys in the UI thread
4861            // Bubble up the key event as WebView doesn't handle it
4862            return false;
4863        }
4864
4865        if (isEnterActionKey(keyCode)) {
4866            // remove the long press message first
4867            mPrivateHandler.removeMessages(LONG_PRESS_CENTER);
4868            mGotCenterDown = false;
4869
4870            if (mSelectingText) {
4871                if (mExtendSelection) {
4872                    copySelection();
4873                    selectionDone();
4874                } else {
4875                    mExtendSelection = true;
4876                    nativeSetExtendSelection();
4877                    invalidate(); // draw the i-beam instead of the arrow
4878                }
4879                return true; // discard press if copy in progress
4880            }
4881
4882            // perform the single click
4883            Rect visibleRect = sendOurVisibleRect();
4884            // Note that sendOurVisibleRect calls viewToContent, so the
4885            // coordinates should be in content coordinates.
4886            if (!nativeCursorIntersects(visibleRect)) {
4887                return false;
4888            }
4889            WebViewCore.CursorData data = cursorData();
4890            mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, data);
4891            playSoundEffect(SoundEffectConstants.CLICK);
4892            if (nativeCursorIsTextInput()) {
4893                rebuildWebTextView();
4894                centerKeyPressOnTextField();
4895                if (inEditingMode()) {
4896                    mWebTextView.setDefaultSelection();
4897                }
4898                return true;
4899            }
4900            clearTextEntry();
4901            nativeShowCursorTimed();
4902            if (mCallbackProxy.uiOverrideUrlLoading(nativeCursorText())) {
4903                return true;
4904            }
4905            if (nativeCursorNodePointer() != 0 && !nativeCursorWantsKeyEvents()) {
4906                mWebViewCore.sendMessage(EventHub.CLICK, data.mFrame,
4907                        nativeCursorNodePointer());
4908                return true;
4909            }
4910        }
4911
4912        // TODO: should we pass all the keys to DOM or check the meta tag
4913        if (nativeCursorWantsKeyEvents() || true) {
4914            // pass the key to DOM
4915            mWebViewCore.sendMessage(EventHub.KEY_UP, event);
4916            // return true as DOM handles the key
4917            return true;
4918        }
4919
4920        // Bubble up the key event as WebView doesn't handle it
4921        return false;
4922    }
4923
4924    /*
4925     * Enter selecting text mode, and see if CAB should be shown.
4926     * Returns true if the WebView is now in
4927     * selecting text mode (including if it was already in that mode, and this
4928     * method did nothing).
4929     */
4930    private boolean setUpSelect(boolean selectWord, int x, int y) {
4931        if (0 == mNativeClass) return false; // client isn't initialized
4932        if (inFullScreenMode()) return false;
4933        if (mSelectingText) return true;
4934        nativeResetSelection();
4935        if (selectWord && !nativeWordSelection(x, y)) {
4936            selectionDone();
4937            return false;
4938        }
4939        mSelectCallback = new SelectActionModeCallback();
4940        mSelectCallback.setWebView(this);
4941        if (startActionMode(mSelectCallback) == null) {
4942            // There is no ActionMode, so do not allow the user to modify a
4943            // selection.
4944            selectionDone();
4945            return false;
4946        }
4947        mExtendSelection = false;
4948        mSelectingText = mDrawSelectionPointer = true;
4949        // don't let the picture change during text selection
4950        WebViewCore.pauseUpdatePicture(mWebViewCore);
4951        if (nativeHasCursorNode()) {
4952            Rect rect = nativeCursorNodeBounds();
4953            mSelectX = contentToViewX(rect.left);
4954            mSelectY = contentToViewY(rect.top);
4955        } else if (mLastTouchY > getVisibleTitleHeight()) {
4956            mSelectX = mScrollX + mLastTouchX;
4957            mSelectY = mScrollY + mLastTouchY;
4958        } else {
4959            mSelectX = mScrollX + getViewWidth() / 2;
4960            mSelectY = mScrollY + getViewHeightWithTitle() / 2;
4961        }
4962        nativeHideCursor();
4963        mMinAutoScrollX = 0;
4964        mMaxAutoScrollX = getViewWidth();
4965        mMinAutoScrollY = 0;
4966        mMaxAutoScrollY = getViewHeightWithTitle();
4967        mScrollingLayer = nativeScrollableLayer(viewToContentX(mSelectX),
4968                viewToContentY(mSelectY), mScrollingLayerRect,
4969                mScrollingLayerBounds);
4970        if (mScrollingLayer != 0) {
4971            if (mScrollingLayerRect.left != mScrollingLayerRect.right) {
4972                mMinAutoScrollX = Math.max(mMinAutoScrollX,
4973                        contentToViewX(mScrollingLayerBounds.left));
4974                mMaxAutoScrollX = Math.min(mMaxAutoScrollX,
4975                        contentToViewX(mScrollingLayerBounds.right));
4976            }
4977            if (mScrollingLayerRect.top != mScrollingLayerRect.bottom) {
4978                mMinAutoScrollY = Math.max(mMinAutoScrollY,
4979                        contentToViewY(mScrollingLayerBounds.top));
4980                mMaxAutoScrollY = Math.min(mMaxAutoScrollY,
4981                        contentToViewY(mScrollingLayerBounds.bottom));
4982            }
4983        }
4984        mMinAutoScrollX += SELECT_SCROLL;
4985        mMaxAutoScrollX -= SELECT_SCROLL;
4986        mMinAutoScrollY += SELECT_SCROLL;
4987        mMaxAutoScrollY -= SELECT_SCROLL;
4988        return true;
4989    }
4990
4991    /**
4992     * Use this method to put the WebView into text selection mode.
4993     * Do not rely on this functionality; it will be deprecated in the future.
4994     */
4995    public void emulateShiftHeld() {
4996        setUpSelect(false, 0, 0);
4997    }
4998
4999    /**
5000     * Select all of the text in this WebView.
5001     *
5002     * @hide pending API council approval.
5003     */
5004    public void selectAll() {
5005        if (0 == mNativeClass) return; // client isn't initialized
5006        if (inFullScreenMode()) return;
5007        if (!mSelectingText) {
5008            // retrieve a point somewhere within the text
5009            Point select = nativeSelectableText();
5010            if (!selectText(select.x, select.y)) return;
5011        }
5012        nativeSelectAll();
5013        mDrawSelectionPointer = false;
5014        mExtendSelection = true;
5015        invalidate();
5016    }
5017
5018    /**
5019     * Called when the selection has been removed.
5020     */
5021    void selectionDone() {
5022        if (mSelectingText) {
5023            mSelectingText = false;
5024            // finish is idempotent, so this is fine even if selectionDone was
5025            // called by mSelectCallback.onDestroyActionMode
5026            mSelectCallback.finish();
5027            mSelectCallback = null;
5028            WebViewCore.resumePriority();
5029            WebViewCore.resumeUpdatePicture(mWebViewCore);
5030            invalidate(); // redraw without selection
5031            mAutoScrollX = 0;
5032            mAutoScrollY = 0;
5033            mSentAutoScrollMessage = false;
5034        }
5035    }
5036
5037    /**
5038     * Copy the selection to the clipboard
5039     *
5040     * @hide pending API council approval.
5041     */
5042    public boolean copySelection() {
5043        boolean copiedSomething = false;
5044        String selection = getSelection();
5045        if (selection != null && selection != "") {
5046            if (DebugFlags.WEB_VIEW) {
5047                Log.v(LOGTAG, "copySelection \"" + selection + "\"");
5048            }
5049            Toast.makeText(mContext
5050                    , com.android.internal.R.string.text_copied
5051                    , Toast.LENGTH_SHORT).show();
5052            copiedSomething = true;
5053            ClipboardManager cm = (ClipboardManager)getContext()
5054                    .getSystemService(Context.CLIPBOARD_SERVICE);
5055            cm.setText(selection);
5056        }
5057        invalidate(); // remove selection region and pointer
5058        return copiedSomething;
5059    }
5060
5061    /**
5062     * @hide pending API Council approval.
5063     */
5064    public SearchBox getSearchBox() {
5065        if ((mWebViewCore == null) || (mWebViewCore.getBrowserFrame() == null)) {
5066            return null;
5067        }
5068        return mWebViewCore.getBrowserFrame().getSearchBox();
5069    }
5070
5071    /**
5072     * Returns the currently highlighted text as a string.
5073     */
5074    String getSelection() {
5075        if (mNativeClass == 0) return "";
5076        return nativeGetSelection();
5077    }
5078
5079    @Override
5080    protected void onAttachedToWindow() {
5081        super.onAttachedToWindow();
5082        if (hasWindowFocus()) setActive(true);
5083        final ViewTreeObserver treeObserver = getViewTreeObserver();
5084        if (mGlobalLayoutListener == null) {
5085            mGlobalLayoutListener = new InnerGlobalLayoutListener();
5086            treeObserver.addOnGlobalLayoutListener(mGlobalLayoutListener);
5087        }
5088        if (mScrollChangedListener == null) {
5089            mScrollChangedListener = new InnerScrollChangedListener();
5090            treeObserver.addOnScrollChangedListener(mScrollChangedListener);
5091        }
5092
5093        addAccessibilityApisToJavaScript();
5094
5095        mTouchEventQueue.reset();
5096    }
5097
5098    @Override
5099    protected void onDetachedFromWindow() {
5100        clearHelpers();
5101        mZoomManager.dismissZoomPicker();
5102        if (hasWindowFocus()) setActive(false);
5103
5104        final ViewTreeObserver treeObserver = getViewTreeObserver();
5105        if (mGlobalLayoutListener != null) {
5106            treeObserver.removeGlobalOnLayoutListener(mGlobalLayoutListener);
5107            mGlobalLayoutListener = null;
5108        }
5109        if (mScrollChangedListener != null) {
5110            treeObserver.removeOnScrollChangedListener(mScrollChangedListener);
5111            mScrollChangedListener = null;
5112        }
5113
5114        removeAccessibilityApisFromJavaScript();
5115
5116        super.onDetachedFromWindow();
5117    }
5118
5119    @Override
5120    protected void onVisibilityChanged(View changedView, int visibility) {
5121        super.onVisibilityChanged(changedView, visibility);
5122        // The zoomManager may be null if the webview is created from XML that
5123        // specifies the view's visibility param as not visible (see http://b/2794841)
5124        if (visibility != View.VISIBLE && mZoomManager != null) {
5125            mZoomManager.dismissZoomPicker();
5126        }
5127    }
5128
5129    /**
5130     * @deprecated WebView no longer needs to implement
5131     * ViewGroup.OnHierarchyChangeListener.  This method does nothing now.
5132     */
5133    @Deprecated
5134    public void onChildViewAdded(View parent, View child) {}
5135
5136    /**
5137     * @deprecated WebView no longer needs to implement
5138     * ViewGroup.OnHierarchyChangeListener.  This method does nothing now.
5139     */
5140    @Deprecated
5141    public void onChildViewRemoved(View p, View child) {}
5142
5143    /**
5144     * @deprecated WebView should not have implemented
5145     * ViewTreeObserver.OnGlobalFocusChangeListener. This method does nothing now.
5146     */
5147    @Deprecated
5148    public void onGlobalFocusChanged(View oldFocus, View newFocus) {
5149    }
5150
5151    void setActive(boolean active) {
5152        if (active) {
5153            if (hasFocus()) {
5154                // If our window regained focus, and we have focus, then begin
5155                // drawing the cursor ring
5156                mDrawCursorRing = true;
5157                setFocusControllerActive(true);
5158                if (mNativeClass != 0) {
5159                    nativeRecordButtons(true, false, true);
5160                }
5161            } else {
5162                if (!inEditingMode()) {
5163                    // If our window gained focus, but we do not have it, do not
5164                    // draw the cursor ring.
5165                    mDrawCursorRing = false;
5166                    setFocusControllerActive(false);
5167                }
5168                // We do not call nativeRecordButtons here because we assume
5169                // that when we lost focus, or window focus, it got called with
5170                // false for the first parameter
5171            }
5172        } else {
5173            if (!mZoomManager.isZoomPickerVisible()) {
5174                /*
5175                 * The external zoom controls come in their own window, so our
5176                 * window loses focus. Our policy is to not draw the cursor ring
5177                 * if our window is not focused, but this is an exception since
5178                 * the user can still navigate the web page with the zoom
5179                 * controls showing.
5180                 */
5181                mDrawCursorRing = false;
5182            }
5183            mKeysPressed.clear();
5184            mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
5185            mTouchMode = TOUCH_DONE_MODE;
5186            if (mNativeClass != 0) {
5187                nativeRecordButtons(false, false, true);
5188            }
5189            setFocusControllerActive(false);
5190        }
5191        invalidate();
5192    }
5193
5194    // To avoid drawing the cursor ring, and remove the TextView when our window
5195    // loses focus.
5196    @Override
5197    public void onWindowFocusChanged(boolean hasWindowFocus) {
5198        setActive(hasWindowFocus);
5199        if (hasWindowFocus) {
5200            JWebCoreJavaBridge.setActiveWebView(this);
5201        } else {
5202            JWebCoreJavaBridge.removeActiveWebView(this);
5203        }
5204        super.onWindowFocusChanged(hasWindowFocus);
5205    }
5206
5207    /*
5208     * Pass a message to WebCore Thread, telling the WebCore::Page's
5209     * FocusController to be  "inactive" so that it will
5210     * not draw the blinking cursor.  It gets set to "active" to draw the cursor
5211     * in WebViewCore.cpp, when the WebCore thread receives key events/clicks.
5212     */
5213    /* package */ void setFocusControllerActive(boolean active) {
5214        if (mWebViewCore == null) return;
5215        mWebViewCore.sendMessage(EventHub.SET_ACTIVE, active ? 1 : 0, 0);
5216        // Need to send this message after the document regains focus.
5217        if (active && mListBoxMessage != null) {
5218            mWebViewCore.sendMessage(mListBoxMessage);
5219            mListBoxMessage = null;
5220        }
5221    }
5222
5223    @Override
5224    protected void onFocusChanged(boolean focused, int direction,
5225            Rect previouslyFocusedRect) {
5226        if (DebugFlags.WEB_VIEW) {
5227            Log.v(LOGTAG, "MT focusChanged " + focused + ", " + direction);
5228        }
5229        if (focused) {
5230            // When we regain focus, if we have window focus, resume drawing
5231            // the cursor ring
5232            if (hasWindowFocus()) {
5233                mDrawCursorRing = true;
5234                if (mNativeClass != 0) {
5235                    nativeRecordButtons(true, false, true);
5236                }
5237                setFocusControllerActive(true);
5238            //} else {
5239                // The WebView has gained focus while we do not have
5240                // windowfocus.  When our window lost focus, we should have
5241                // called nativeRecordButtons(false...)
5242            }
5243        } else {
5244            // When we lost focus, unless focus went to the TextView (which is
5245            // true if we are in editing mode), stop drawing the cursor ring.
5246            if (!inEditingMode()) {
5247                mDrawCursorRing = false;
5248                if (mNativeClass != 0) {
5249                    nativeRecordButtons(false, false, true);
5250                }
5251                setFocusControllerActive(false);
5252            }
5253            mKeysPressed.clear();
5254        }
5255
5256        super.onFocusChanged(focused, direction, previouslyFocusedRect);
5257    }
5258
5259    void setGLRectViewport() {
5260        // Use the getGlobalVisibleRect() to get the intersection among the parents
5261        // visible == false means we're clipped - send a null rect down to indicate that
5262        // we should not draw
5263        boolean visible = getGlobalVisibleRect(mGLRectViewport);
5264        if (visible) {
5265            // Then need to invert the Y axis, just for GL
5266            View rootView = getRootView();
5267            int rootViewHeight = rootView.getHeight();
5268            mViewRectViewport.set(mGLRectViewport);
5269            int savedWebViewBottom = mGLRectViewport.bottom;
5270            mGLRectViewport.bottom = rootViewHeight - mGLRectViewport.top - getVisibleTitleHeight();
5271            mGLRectViewport.top = rootViewHeight - savedWebViewBottom;
5272            mGLViewportEmpty = false;
5273        } else {
5274            mGLViewportEmpty = true;
5275        }
5276        nativeUpdateDrawGLFunction(mGLViewportEmpty ? null : mGLRectViewport,
5277                mGLViewportEmpty ? null : mViewRectViewport);
5278    }
5279
5280    /**
5281     * @hide
5282     */
5283    @Override
5284    protected boolean setFrame(int left, int top, int right, int bottom) {
5285        boolean changed = super.setFrame(left, top, right, bottom);
5286        if (!changed && mHeightCanMeasure) {
5287            // When mHeightCanMeasure is true, we will set mLastHeightSent to 0
5288            // in WebViewCore after we get the first layout. We do call
5289            // requestLayout() when we get contentSizeChanged(). But the View
5290            // system won't call onSizeChanged if the dimension is not changed.
5291            // In this case, we need to call sendViewSizeZoom() explicitly to
5292            // notify the WebKit about the new dimensions.
5293            sendViewSizeZoom(false);
5294        }
5295        setGLRectViewport();
5296        return changed;
5297    }
5298
5299    @Override
5300    protected void onSizeChanged(int w, int h, int ow, int oh) {
5301        super.onSizeChanged(w, h, ow, oh);
5302
5303        // adjust the max viewport width depending on the view dimensions. This
5304        // is to ensure the scaling is not going insane. So do not shrink it if
5305        // the view size is temporarily smaller, e.g. when soft keyboard is up.
5306        int newMaxViewportWidth = (int) (Math.max(w, h) / mZoomManager.getDefaultMinZoomScale());
5307        if (newMaxViewportWidth > sMaxViewportWidth) {
5308            sMaxViewportWidth = newMaxViewportWidth;
5309        }
5310
5311        mZoomManager.onSizeChanged(w, h, ow, oh);
5312    }
5313
5314    @Override
5315    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
5316        super.onScrollChanged(l, t, oldl, oldt);
5317        if (!mInOverScrollMode) {
5318            sendOurVisibleRect();
5319            // update WebKit if visible title bar height changed. The logic is same
5320            // as getVisibleTitleHeight.
5321            int titleHeight = getTitleHeight();
5322            if (Math.max(titleHeight - t, 0) != Math.max(titleHeight - oldt, 0)) {
5323                sendViewSizeZoom(false);
5324            }
5325        }
5326    }
5327
5328    @Override
5329    public boolean dispatchKeyEvent(KeyEvent event) {
5330        switch (event.getAction()) {
5331            case KeyEvent.ACTION_DOWN:
5332                mKeysPressed.add(Integer.valueOf(event.getKeyCode()));
5333                break;
5334            case KeyEvent.ACTION_MULTIPLE:
5335                // Always accept the action.
5336                break;
5337            case KeyEvent.ACTION_UP:
5338                int location = mKeysPressed.indexOf(Integer.valueOf(event.getKeyCode()));
5339                if (location == -1) {
5340                    // We did not receive the key down for this key, so do not
5341                    // handle the key up.
5342                    return false;
5343                } else {
5344                    // We did receive the key down.  Handle the key up, and
5345                    // remove it from our pressed keys.
5346                    mKeysPressed.remove(location);
5347                }
5348                break;
5349            default:
5350                // Accept the action.  This should not happen, unless a new
5351                // action is added to KeyEvent.
5352                break;
5353        }
5354        if (inEditingMode() && mWebTextView.isFocused()) {
5355            // Ensure that the WebTextView gets the event, even if it does
5356            // not currently have a bounds.
5357            return mWebTextView.dispatchKeyEvent(event);
5358        } else {
5359            return super.dispatchKeyEvent(event);
5360        }
5361    }
5362
5363    // Here are the snap align logic:
5364    // 1. If it starts nearly horizontally or vertically, snap align;
5365    // 2. If there is a dramitic direction change, let it go;
5366    // 3. If there is a same direction back and forth, lock it.
5367
5368    // adjustable parameters
5369    private int mMinLockSnapReverseDistance;
5370    private static final float MAX_SLOPE_FOR_DIAG = 1.5f;
5371    private static final int MIN_BREAK_SNAP_CROSS_DISTANCE = 80;
5372
5373    private boolean hitFocusedPlugin(int contentX, int contentY) {
5374        if (DebugFlags.WEB_VIEW) {
5375            Log.v(LOGTAG, "nativeFocusIsPlugin()=" + nativeFocusIsPlugin());
5376            Rect r = nativeFocusNodeBounds();
5377            Log.v(LOGTAG, "nativeFocusNodeBounds()=(" + r.left + ", " + r.top
5378                    + ", " + r.right + ", " + r.bottom + ")");
5379        }
5380        return nativeFocusIsPlugin()
5381                && nativeFocusNodeBounds().contains(contentX, contentY);
5382    }
5383
5384    private boolean shouldForwardTouchEvent() {
5385        return mFullScreenHolder != null || (mForwardTouchEvents
5386                && !mSelectingText
5387                && mPreventDefault != PREVENT_DEFAULT_IGNORE
5388                && mPreventDefault != PREVENT_DEFAULT_NO);
5389    }
5390
5391    private boolean inFullScreenMode() {
5392        return mFullScreenHolder != null;
5393    }
5394
5395    private void dismissFullScreenMode() {
5396        if (inFullScreenMode()) {
5397            mFullScreenHolder.dismiss();
5398            mFullScreenHolder = null;
5399        }
5400    }
5401
5402    void onPinchToZoomAnimationStart() {
5403        // cancel the single touch handling
5404        cancelTouch();
5405        onZoomAnimationStart();
5406    }
5407
5408    void onPinchToZoomAnimationEnd(ScaleGestureDetector detector) {
5409        onZoomAnimationEnd();
5410        // start a drag, TOUCH_PINCH_DRAG, can't use TOUCH_INIT_MODE as
5411        // it may trigger the unwanted click, can't use TOUCH_DRAG_MODE
5412        // as it may trigger the unwanted fling.
5413        mTouchMode = TOUCH_PINCH_DRAG;
5414        mConfirmMove = true;
5415        startTouch(detector.getFocusX(), detector.getFocusY(), mLastTouchTime);
5416    }
5417
5418    // See if there is a layer at x, y and switch to TOUCH_DRAG_LAYER_MODE if a
5419    // layer is found.
5420    private void startScrollingLayer(float x, float y) {
5421        int contentX = viewToContentX((int) x + mScrollX);
5422        int contentY = viewToContentY((int) y + mScrollY);
5423        mScrollingLayer = nativeScrollableLayer(contentX, contentY,
5424                mScrollingLayerRect, mScrollingLayerBounds);
5425        if (mScrollingLayer != 0) {
5426            mTouchMode = TOUCH_DRAG_LAYER_MODE;
5427        }
5428    }
5429
5430    // 1/(density * density) used to compute the distance between points.
5431    // Computed in init().
5432    private float DRAG_LAYER_INVERSE_DENSITY_SQUARED;
5433
5434    // The distance between two points reported in onTouchEvent scaled by the
5435    // density of the screen.
5436    private static final int DRAG_LAYER_FINGER_DISTANCE = 20000;
5437
5438    @Override
5439    public boolean onTouchEvent(MotionEvent ev) {
5440        if (mNativeClass == 0 || (!isClickable() && !isLongClickable())) {
5441            return false;
5442        }
5443
5444        if (DebugFlags.WEB_VIEW) {
5445            Log.v(LOGTAG, ev + " at " + ev.getEventTime()
5446                + " mTouchMode=" + mTouchMode
5447                + " numPointers=" + ev.getPointerCount());
5448        }
5449
5450        // If WebKit wasn't interested in this multitouch gesture, enqueue
5451        // the event for handling directly rather than making the round trip
5452        // to WebKit and back.
5453        if (ev.getPointerCount() > 1 && mPreventDefault != PREVENT_DEFAULT_NO) {
5454            passMultiTouchToWebKit(ev, mTouchEventQueue.nextTouchSequence());
5455        } else {
5456            mTouchEventQueue.enqueueTouchEvent(ev);
5457        }
5458
5459        // Since all events are handled asynchronously, we always want the gesture stream.
5460        return true;
5461    }
5462
5463    /*
5464     * Common code for single touch and multi-touch.
5465     * (x, y) denotes current focus point, which is the touch point for single touch
5466     * and the middle point for multi-touch.
5467     */
5468    private boolean handleTouchEventCommon(MotionEvent ev, int action, int x, int y) {
5469        long eventTime = ev.getEventTime();
5470
5471        // Due to the touch screen edge effect, a touch closer to the edge
5472        // always snapped to the edge. As getViewWidth() can be different from
5473        // getWidth() due to the scrollbar, adjusting the point to match
5474        // getViewWidth(). Same applied to the height.
5475        x = Math.min(x, getViewWidth() - 1);
5476        y = Math.min(y, getViewHeightWithTitle() - 1);
5477
5478        int deltaX = mLastTouchX - x;
5479        int deltaY = mLastTouchY - y;
5480        int contentX = viewToContentX(x + mScrollX);
5481        int contentY = viewToContentY(y + mScrollY);
5482
5483        switch (action) {
5484            case MotionEvent.ACTION_DOWN: {
5485                mPreventDefault = PREVENT_DEFAULT_NO;
5486                mConfirmMove = false;
5487                mInitialHitTestResult = null;
5488                if (!mScroller.isFinished()) {
5489                    // stop the current scroll animation, but if this is
5490                    // the start of a fling, allow it to add to the current
5491                    // fling's velocity
5492                    mScroller.abortAnimation();
5493                    mTouchMode = TOUCH_DRAG_START_MODE;
5494                    mConfirmMove = true;
5495                    mPrivateHandler.removeMessages(RESUME_WEBCORE_PRIORITY);
5496                } else if (mPrivateHandler.hasMessages(RELEASE_SINGLE_TAP)) {
5497                    mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP);
5498                    if (getSettings().supportTouchOnly()) {
5499                        removeTouchHighlight(true);
5500                    }
5501                    if (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare) {
5502                        mTouchMode = TOUCH_DOUBLE_TAP_MODE;
5503                    } else {
5504                        // commit the short press action for the previous tap
5505                        doShortPress();
5506                        mTouchMode = TOUCH_INIT_MODE;
5507                        mDeferTouchProcess = (!inFullScreenMode()
5508                                && mForwardTouchEvents) ? hitFocusedPlugin(
5509                                contentX, contentY) : false;
5510                    }
5511                } else { // the normal case
5512                    mTouchMode = TOUCH_INIT_MODE;
5513                    mDeferTouchProcess = (!inFullScreenMode()
5514                            && mForwardTouchEvents) ? hitFocusedPlugin(
5515                            contentX, contentY) : false;
5516                    mWebViewCore.sendMessage(
5517                            EventHub.UPDATE_FRAME_CACHE_IF_LOADING);
5518                    if (getSettings().supportTouchOnly()) {
5519                        TouchHighlightData data = new TouchHighlightData();
5520                        data.mX = contentX;
5521                        data.mY = contentY;
5522                        data.mSlop = viewToContentDimension(mNavSlop);
5523                        mWebViewCore.sendMessageDelayed(
5524                                EventHub.GET_TOUCH_HIGHLIGHT_RECTS, data,
5525                                ViewConfiguration.getTapTimeout());
5526                        if (DEBUG_TOUCH_HIGHLIGHT) {
5527                            if (getSettings().getNavDump()) {
5528                                mTouchHighlightX = (int) x + mScrollX;
5529                                mTouchHighlightY = (int) y + mScrollY;
5530                                mPrivateHandler.postDelayed(new Runnable() {
5531                                    public void run() {
5532                                        mTouchHighlightX = mTouchHighlightY = 0;
5533                                        invalidate();
5534                                    }
5535                                }, TOUCH_HIGHLIGHT_ELAPSE_TIME);
5536                            }
5537                        }
5538                    }
5539                    if (mLogEvent && eventTime - mLastTouchUpTime < 1000) {
5540                        EventLog.writeEvent(EventLogTags.BROWSER_DOUBLE_TAP_DURATION,
5541                                (eventTime - mLastTouchUpTime), eventTime);
5542                    }
5543                    if (mSelectingText) {
5544                        mDrawSelectionPointer = false;
5545                        mSelectionStarted = nativeStartSelection(contentX, contentY);
5546                        if (DebugFlags.WEB_VIEW) {
5547                            Log.v(LOGTAG, "select=" + contentX + "," + contentY);
5548                        }
5549                        invalidate();
5550                    }
5551                }
5552                // Trigger the link
5553                if (!mSelectingText && (mTouchMode == TOUCH_INIT_MODE
5554                        || mTouchMode == TOUCH_DOUBLE_TAP_MODE)) {
5555                    mPrivateHandler.sendEmptyMessageDelayed(
5556                            SWITCH_TO_SHORTPRESS, TAP_TIMEOUT);
5557                    mPrivateHandler.sendEmptyMessageDelayed(
5558                            SWITCH_TO_LONGPRESS, LONG_PRESS_TIMEOUT);
5559                    if (inFullScreenMode() || mDeferTouchProcess) {
5560                        mPreventDefault = PREVENT_DEFAULT_YES;
5561                    } else if (mForwardTouchEvents) {
5562                        mPreventDefault = PREVENT_DEFAULT_MAYBE_YES;
5563                    } else {
5564                        mPreventDefault = PREVENT_DEFAULT_NO;
5565                    }
5566                    // pass the touch events from UI thread to WebCore thread
5567                    if (shouldForwardTouchEvent()) {
5568                        TouchEventData ted = new TouchEventData();
5569                        ted.mAction = action;
5570                        ted.mIds = new int[1];
5571                        ted.mIds[0] = ev.getPointerId(0);
5572                        ted.mPoints = new Point[1];
5573                        ted.mPoints[0] = new Point(contentX, contentY);
5574                        ted.mPointsInView = new Point[1];
5575                        ted.mPointsInView[0] = new Point(x, y);
5576                        ted.mMetaState = ev.getMetaState();
5577                        ted.mReprocess = mDeferTouchProcess;
5578                        ted.mNativeLayer = nativeScrollableLayer(
5579                                contentX, contentY, ted.mNativeLayerRect, null);
5580                        ted.mSequence = mTouchEventQueue.nextTouchSequence();
5581                        mTouchEventQueue.preQueueTouchEventData(ted);
5582                        mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
5583                        if (mDeferTouchProcess) {
5584                            // still needs to set them for compute deltaX/Y
5585                            mLastTouchX = x;
5586                            mLastTouchY = y;
5587                            break;
5588                        }
5589                        if (!inFullScreenMode()) {
5590                            mPrivateHandler.removeMessages(PREVENT_DEFAULT_TIMEOUT);
5591                            mPrivateHandler.sendMessageDelayed(mPrivateHandler
5592                                    .obtainMessage(PREVENT_DEFAULT_TIMEOUT,
5593                                            action, 0), TAP_TIMEOUT);
5594                        }
5595                    }
5596                }
5597                startTouch(x, y, eventTime);
5598                break;
5599            }
5600            case MotionEvent.ACTION_MOVE: {
5601                boolean firstMove = false;
5602                if (!mConfirmMove && (deltaX * deltaX + deltaY * deltaY)
5603                        >= mTouchSlopSquare) {
5604                    mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
5605                    mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
5606                    mConfirmMove = true;
5607                    firstMove = true;
5608                    if (mTouchMode == TOUCH_DOUBLE_TAP_MODE) {
5609                        mTouchMode = TOUCH_INIT_MODE;
5610                    }
5611                    if (getSettings().supportTouchOnly()) {
5612                        removeTouchHighlight(true);
5613                    }
5614                }
5615                // pass the touch events from UI thread to WebCore thread
5616                if (shouldForwardTouchEvent() && mConfirmMove && (firstMove
5617                        || eventTime - mLastSentTouchTime > mCurrentTouchInterval)) {
5618                    TouchEventData ted = new TouchEventData();
5619                    ted.mAction = action;
5620                    ted.mIds = new int[1];
5621                    ted.mIds[0] = ev.getPointerId(0);
5622                    ted.mPoints = new Point[1];
5623                    ted.mPoints[0] = new Point(contentX, contentY);
5624                    ted.mPointsInView = new Point[1];
5625                    ted.mPointsInView[0] = new Point(x, y);
5626                    ted.mMetaState = ev.getMetaState();
5627                    ted.mReprocess = mDeferTouchProcess;
5628                    ted.mNativeLayer = mScrollingLayer;
5629                    ted.mNativeLayerRect.set(mScrollingLayerRect);
5630                    ted.mSequence = mTouchEventQueue.nextTouchSequence();
5631                    mTouchEventQueue.preQueueTouchEventData(ted);
5632                    mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
5633                    mLastSentTouchTime = eventTime;
5634                    if (mDeferTouchProcess) {
5635                        break;
5636                    }
5637                    if (firstMove && !inFullScreenMode()) {
5638                        mPrivateHandler.sendMessageDelayed(mPrivateHandler
5639                                .obtainMessage(PREVENT_DEFAULT_TIMEOUT,
5640                                        action, 0), TAP_TIMEOUT);
5641                    }
5642                }
5643                if (mTouchMode == TOUCH_DONE_MODE
5644                        || mPreventDefault == PREVENT_DEFAULT_YES) {
5645                    // no dragging during scroll zoom animation, or when prevent
5646                    // default is yes
5647                    break;
5648                }
5649                if (mVelocityTracker == null) {
5650                    Log.e(LOGTAG, "Got null mVelocityTracker when "
5651                            + "mPreventDefault = " + mPreventDefault
5652                            + " mDeferTouchProcess = " + mDeferTouchProcess
5653                            + " mTouchMode = " + mTouchMode);
5654                } else {
5655                    mVelocityTracker.addMovement(ev);
5656                }
5657                if (mSelectingText && mSelectionStarted) {
5658                    if (DebugFlags.WEB_VIEW) {
5659                        Log.v(LOGTAG, "extend=" + contentX + "," + contentY);
5660                    }
5661                    ViewParent parent = getParent();
5662                    if (parent != null) {
5663                        parent.requestDisallowInterceptTouchEvent(true);
5664                    }
5665                    mAutoScrollX = x <= mMinAutoScrollX ? -SELECT_SCROLL
5666                            : x >= mMaxAutoScrollX ? SELECT_SCROLL : 0;
5667                    mAutoScrollY = y <= mMinAutoScrollY ? -SELECT_SCROLL
5668                            : y >= mMaxAutoScrollY ? SELECT_SCROLL : 0;
5669                    if ((mAutoScrollX != 0 || mAutoScrollY != 0)
5670                            && !mSentAutoScrollMessage) {
5671                        mSentAutoScrollMessage = true;
5672                        mPrivateHandler.sendEmptyMessageDelayed(
5673                                SCROLL_SELECT_TEXT, SELECT_SCROLL_INTERVAL);
5674                    }
5675                    if (deltaX != 0 || deltaY != 0) {
5676                        nativeExtendSelection(contentX, contentY);
5677                        invalidate();
5678                    }
5679                    break;
5680                }
5681
5682                if (mTouchMode != TOUCH_DRAG_MODE &&
5683                        mTouchMode != TOUCH_DRAG_LAYER_MODE) {
5684
5685                    if (!mConfirmMove) {
5686                        break;
5687                    }
5688
5689                    if (mPreventDefault == PREVENT_DEFAULT_MAYBE_YES
5690                            || mPreventDefault == PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN) {
5691                        // track mLastTouchTime as we may need to do fling at
5692                        // ACTION_UP
5693                        mLastTouchTime = eventTime;
5694                        break;
5695                    }
5696
5697                    // Only lock dragging to one axis if we don't have a scale in progress.
5698                    // Scaling implies free-roaming movement. Note this is only ever a question
5699                    // if mZoomManager.supportsPanDuringZoom() is true.
5700                    final ScaleGestureDetector detector =
5701                      mZoomManager.getMultiTouchGestureDetector();
5702                    if (detector == null || !detector.isInProgress()) {
5703                        // if it starts nearly horizontal or vertical, enforce it
5704                        int ax = Math.abs(deltaX);
5705                        int ay = Math.abs(deltaY);
5706                        if (ax > MAX_SLOPE_FOR_DIAG * ay) {
5707                            mSnapScrollMode = SNAP_X;
5708                            mSnapPositive = deltaX > 0;
5709                        } else if (ay > MAX_SLOPE_FOR_DIAG * ax) {
5710                            mSnapScrollMode = SNAP_Y;
5711                            mSnapPositive = deltaY > 0;
5712                        }
5713                    }
5714
5715                    mTouchMode = TOUCH_DRAG_MODE;
5716                    mLastTouchX = x;
5717                    mLastTouchY = y;
5718                    deltaX = 0;
5719                    deltaY = 0;
5720
5721                    startScrollingLayer(x, y);
5722                    startDrag();
5723                }
5724
5725                // do pan
5726                boolean done = false;
5727                boolean keepScrollBarsVisible = false;
5728                if (deltaX == 0 && deltaY == 0) {
5729                    keepScrollBarsVisible = done = true;
5730                } else {
5731                    if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_Y) {
5732                        int ax = Math.abs(deltaX);
5733                        int ay = Math.abs(deltaY);
5734                        if (mSnapScrollMode == SNAP_X) {
5735                            // radical change means getting out of snap mode
5736                            if (ay > MAX_SLOPE_FOR_DIAG * ax
5737                                    && ay > MIN_BREAK_SNAP_CROSS_DISTANCE) {
5738                                mSnapScrollMode = SNAP_NONE;
5739                            }
5740                            // reverse direction means lock in the snap mode
5741                            if (ax > MAX_SLOPE_FOR_DIAG * ay &&
5742                                    (mSnapPositive
5743                                    ? deltaX < -mMinLockSnapReverseDistance
5744                                    : deltaX > mMinLockSnapReverseDistance)) {
5745                                mSnapScrollMode |= SNAP_LOCK;
5746                            }
5747                        } else {
5748                            // radical change means getting out of snap mode
5749                            if (ax > MAX_SLOPE_FOR_DIAG * ay
5750                                    && ax > MIN_BREAK_SNAP_CROSS_DISTANCE) {
5751                                mSnapScrollMode = SNAP_NONE;
5752                            }
5753                            // reverse direction means lock in the snap mode
5754                            if (ay > MAX_SLOPE_FOR_DIAG * ax &&
5755                                    (mSnapPositive
5756                                    ? deltaY < -mMinLockSnapReverseDistance
5757                                    : deltaY > mMinLockSnapReverseDistance)) {
5758                                mSnapScrollMode |= SNAP_LOCK;
5759                            }
5760                        }
5761                    }
5762                    if (mSnapScrollMode != SNAP_NONE) {
5763                        if ((mSnapScrollMode & SNAP_X) == SNAP_X) {
5764                            deltaY = 0;
5765                        } else {
5766                            deltaX = 0;
5767                        }
5768                    }
5769                    if ((deltaX | deltaY) != 0) {
5770                        if (deltaX != 0) {
5771                            mLastTouchX = x;
5772                        }
5773                        if (deltaY != 0) {
5774                            mLastTouchY = y;
5775                        }
5776                        mHeldMotionless = MOTIONLESS_FALSE;
5777                    }
5778                    mLastTouchTime = eventTime;
5779                }
5780
5781                doDrag(deltaX, deltaY);
5782
5783                // Turn off scrollbars when dragging a layer.
5784                if (keepScrollBarsVisible &&
5785                        mTouchMode != TOUCH_DRAG_LAYER_MODE) {
5786                    if (mHeldMotionless != MOTIONLESS_TRUE) {
5787                        mHeldMotionless = MOTIONLESS_TRUE;
5788                        invalidate();
5789                    }
5790                    // keep the scrollbar on the screen even there is no scroll
5791                    awakenScrollBars(ViewConfiguration.getScrollDefaultDelay(),
5792                            false);
5793                    // return false to indicate that we can't pan out of the
5794                    // view space
5795                    return !done;
5796                }
5797                break;
5798            }
5799            case MotionEvent.ACTION_UP: {
5800                if (!isFocused()) requestFocus();
5801                // pass the touch events from UI thread to WebCore thread
5802                if (shouldForwardTouchEvent()) {
5803                    TouchEventData ted = new TouchEventData();
5804                    ted.mIds = new int[1];
5805                    ted.mIds[0] = ev.getPointerId(0);
5806                    ted.mAction = action;
5807                    ted.mPoints = new Point[1];
5808                    ted.mPoints[0] = new Point(contentX, contentY);
5809                    ted.mPointsInView = new Point[1];
5810                    ted.mPointsInView[0] = new Point(x, y);
5811                    ted.mMetaState = ev.getMetaState();
5812                    ted.mReprocess = mDeferTouchProcess;
5813                    ted.mNativeLayer = mScrollingLayer;
5814                    ted.mNativeLayerRect.set(mScrollingLayerRect);
5815                    ted.mSequence = mTouchEventQueue.nextTouchSequence();
5816                    mTouchEventQueue.preQueueTouchEventData(ted);
5817                    mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
5818                }
5819                mLastTouchUpTime = eventTime;
5820                if (mSentAutoScrollMessage) {
5821                    mAutoScrollX = mAutoScrollY = 0;
5822                }
5823                switch (mTouchMode) {
5824                    case TOUCH_DOUBLE_TAP_MODE: // double tap
5825                        mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
5826                        mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
5827                        if (inFullScreenMode() || mDeferTouchProcess) {
5828                            TouchEventData ted = new TouchEventData();
5829                            ted.mIds = new int[1];
5830                            ted.mIds[0] = ev.getPointerId(0);
5831                            ted.mAction = WebViewCore.ACTION_DOUBLETAP;
5832                            ted.mPoints = new Point[1];
5833                            ted.mPoints[0] = new Point(contentX, contentY);
5834                            ted.mPointsInView = new Point[1];
5835                            ted.mPointsInView[0] = new Point(x, y);
5836                            ted.mMetaState = ev.getMetaState();
5837                            ted.mReprocess = mDeferTouchProcess;
5838                            ted.mNativeLayer = nativeScrollableLayer(
5839                                    contentX, contentY,
5840                                    ted.mNativeLayerRect, null);
5841                            ted.mSequence = mTouchEventQueue.nextTouchSequence();
5842                            mTouchEventQueue.preQueueTouchEventData(ted);
5843                            mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
5844                        } else if (mPreventDefault != PREVENT_DEFAULT_YES){
5845                            mZoomManager.handleDoubleTap(mLastTouchX, mLastTouchY);
5846                            mTouchMode = TOUCH_DONE_MODE;
5847                        }
5848                        break;
5849                    case TOUCH_INIT_MODE: // tap
5850                    case TOUCH_SHORTPRESS_START_MODE:
5851                    case TOUCH_SHORTPRESS_MODE:
5852                        mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
5853                        mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
5854                        if (mConfirmMove) {
5855                            Log.w(LOGTAG, "Miss a drag as we are waiting for" +
5856                                    " WebCore's response for touch down.");
5857                            if (mPreventDefault != PREVENT_DEFAULT_YES
5858                                    && (computeMaxScrollX() > 0
5859                                            || computeMaxScrollY() > 0)) {
5860                                // If the user has performed a very quick touch
5861                                // sequence it is possible that we may get here
5862                                // before WebCore has had a chance to process the events.
5863                                // In this case, any call to preventDefault in the
5864                                // JS touch handler will not have been executed yet.
5865                                // Hence we will see both the UI (now) and WebCore
5866                                // (when context switches) handling the event,
5867                                // regardless of whether the web developer actually
5868                                // doeses preventDefault in their touch handler. This
5869                                // is the nature of our asynchronous touch model.
5870
5871                                // we will not rewrite drag code here, but we
5872                                // will try fling if it applies.
5873                                WebViewCore.reducePriority();
5874                                // to get better performance, pause updating the
5875                                // picture
5876                                WebViewCore.pauseUpdatePicture(mWebViewCore);
5877                                // fall through to TOUCH_DRAG_MODE
5878                            } else {
5879                                // WebKit may consume the touch event and modify
5880                                // DOM. drawContentPicture() will be called with
5881                                // animateSroll as true for better performance.
5882                                // Force redraw in high-quality.
5883                                invalidate();
5884                                break;
5885                            }
5886                        } else {
5887                            if (mSelectingText) {
5888                                // tapping on selection or controls does nothing
5889                                if (!nativeHitSelection(contentX, contentY)) {
5890                                    selectionDone();
5891                                }
5892                                break;
5893                            }
5894                            // only trigger double tap if the WebView is
5895                            // scalable
5896                            if (mTouchMode == TOUCH_INIT_MODE
5897                                    && (canZoomIn() || canZoomOut())) {
5898                                mPrivateHandler.sendEmptyMessageDelayed(
5899                                        RELEASE_SINGLE_TAP, ViewConfiguration
5900                                                .getDoubleTapTimeout());
5901                            } else {
5902                                doShortPress();
5903                            }
5904                            break;
5905                        }
5906                    case TOUCH_DRAG_MODE:
5907                    case TOUCH_DRAG_LAYER_MODE:
5908                        mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
5909                        mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
5910                        // if the user waits a while w/o moving before the
5911                        // up, we don't want to do a fling
5912                        if (eventTime - mLastTouchTime <= MIN_FLING_TIME) {
5913                            if (mVelocityTracker == null) {
5914                                Log.e(LOGTAG, "Got null mVelocityTracker when "
5915                                        + "mPreventDefault = "
5916                                        + mPreventDefault
5917                                        + " mDeferTouchProcess = "
5918                                        + mDeferTouchProcess);
5919                            } else {
5920                                mVelocityTracker.addMovement(ev);
5921                            }
5922                            // set to MOTIONLESS_IGNORE so that it won't keep
5923                            // removing and sending message in
5924                            // drawCoreAndCursorRing()
5925                            mHeldMotionless = MOTIONLESS_IGNORE;
5926                            doFling();
5927                            break;
5928                        } else {
5929                            if (mScroller.springBack(mScrollX, mScrollY, 0,
5930                                    computeMaxScrollX(), 0,
5931                                    computeMaxScrollY())) {
5932                                invalidate();
5933                            }
5934                        }
5935                        // redraw in high-quality, as we're done dragging
5936                        mHeldMotionless = MOTIONLESS_TRUE;
5937                        invalidate();
5938                        // fall through
5939                    case TOUCH_DRAG_START_MODE:
5940                        // TOUCH_DRAG_START_MODE should not happen for the real
5941                        // device as we almost certain will get a MOVE. But this
5942                        // is possible on emulator.
5943                        mLastVelocity = 0;
5944                        WebViewCore.resumePriority();
5945                        if (!mSelectingText) {
5946                            WebViewCore.resumeUpdatePicture(mWebViewCore);
5947                        }
5948                        break;
5949                }
5950                stopTouch();
5951                break;
5952            }
5953            case MotionEvent.ACTION_CANCEL: {
5954                if (mTouchMode == TOUCH_DRAG_MODE) {
5955                    mScroller.springBack(mScrollX, mScrollY, 0,
5956                            computeMaxScrollX(), 0, computeMaxScrollY());
5957                    invalidate();
5958                }
5959                cancelWebCoreTouchEvent(contentX, contentY, false);
5960                cancelTouch();
5961                break;
5962            }
5963        }
5964        return true;
5965    }
5966
5967    private void passMultiTouchToWebKit(MotionEvent ev, long sequence) {
5968        TouchEventData ted = new TouchEventData();
5969        ted.mAction = ev.getActionMasked();
5970        final int count = ev.getPointerCount();
5971        ted.mIds = new int[count];
5972        ted.mPoints = new Point[count];
5973        ted.mPointsInView = new Point[count];
5974        for (int c = 0; c < count; c++) {
5975            ted.mIds[c] = ev.getPointerId(c);
5976            int x = viewToContentX((int) ev.getX(c) + mScrollX);
5977            int y = viewToContentY((int) ev.getY(c) + mScrollY);
5978            ted.mPoints[c] = new Point(x, y);
5979            ted.mPointsInView[c] = new Point((int) ev.getX(c), (int) ev.getY(c));
5980        }
5981        if (ted.mAction == MotionEvent.ACTION_POINTER_DOWN
5982            || ted.mAction == MotionEvent.ACTION_POINTER_UP) {
5983            ted.mActionIndex = ev.getActionIndex();
5984        }
5985        ted.mMetaState = ev.getMetaState();
5986        ted.mReprocess = true;
5987        ted.mMotionEvent = MotionEvent.obtain(ev);
5988        ted.mSequence = sequence;
5989        mTouchEventQueue.preQueueTouchEventData(ted);
5990        mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
5991        cancelLongPress();
5992        mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
5993    }
5994
5995    void handleMultiTouchInWebView(MotionEvent ev) {
5996        if (DebugFlags.WEB_VIEW) {
5997            Log.v(LOGTAG, "multi-touch: " + ev + " at " + ev.getEventTime()
5998                + " mTouchMode=" + mTouchMode
5999                + " numPointers=" + ev.getPointerCount()
6000                + " scrolloffset=(" + mScrollX + "," + mScrollY + ")");
6001        }
6002
6003        final ScaleGestureDetector detector =
6004            mZoomManager.getMultiTouchGestureDetector();
6005
6006        // A few apps use WebView but don't instantiate gesture detector.
6007        // We don't need to support multi touch for them.
6008        if (detector == null) return;
6009
6010        float x = ev.getX();
6011        float y = ev.getY();
6012
6013        if (mPreventDefault != PREVENT_DEFAULT_YES) {
6014            detector.onTouchEvent(ev);
6015
6016            if (detector.isInProgress()) {
6017                if (DebugFlags.WEB_VIEW) {
6018                    Log.v(LOGTAG, "detector is in progress");
6019                }
6020                mLastTouchTime = ev.getEventTime();
6021                x = detector.getFocusX();
6022                y = detector.getFocusY();
6023
6024                cancelLongPress();
6025                mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
6026                if (!mZoomManager.supportsPanDuringZoom()) {
6027                    return;
6028                }
6029                mTouchMode = TOUCH_DRAG_MODE;
6030                if (mVelocityTracker == null) {
6031                    mVelocityTracker = VelocityTracker.obtain();
6032                }
6033            }
6034        }
6035
6036        int action = ev.getActionMasked();
6037        if (action == MotionEvent.ACTION_POINTER_DOWN) {
6038            cancelTouch();
6039            action = MotionEvent.ACTION_DOWN;
6040        } else if (action == MotionEvent.ACTION_POINTER_UP && ev.getPointerCount() == 2) {
6041            // set mLastTouchX/Y to the remaining point
6042            mLastTouchX = Math.round(x);
6043            mLastTouchY = Math.round(y);
6044        } else if (action == MotionEvent.ACTION_MOVE) {
6045            // negative x or y indicate it is on the edge, skip it.
6046            if (x < 0 || y < 0) {
6047                return;
6048            }
6049        }
6050
6051        handleTouchEventCommon(ev, action, Math.round(x), Math.round(y));
6052    }
6053
6054    private void cancelWebCoreTouchEvent(int x, int y, boolean removeEvents) {
6055        if (shouldForwardTouchEvent()) {
6056            if (removeEvents) {
6057                mWebViewCore.removeMessages(EventHub.TOUCH_EVENT);
6058            }
6059            TouchEventData ted = new TouchEventData();
6060            ted.mIds = new int[1];
6061            ted.mIds[0] = 0;
6062            ted.mPoints = new Point[1];
6063            ted.mPoints[0] = new Point(x, y);
6064            ted.mPointsInView = new Point[1];
6065            int viewX = contentToViewX(x) - mScrollX;
6066            int viewY = contentToViewY(y) - mScrollY;
6067            ted.mPointsInView[0] = new Point(viewX, viewY);
6068            ted.mAction = MotionEvent.ACTION_CANCEL;
6069            ted.mNativeLayer = nativeScrollableLayer(
6070                    x, y, ted.mNativeLayerRect, null);
6071            ted.mSequence = mTouchEventQueue.nextTouchSequence();
6072            mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
6073            mPreventDefault = PREVENT_DEFAULT_IGNORE;
6074
6075            if (removeEvents) {
6076                // Mark this after sending the message above; we should
6077                // be willing to ignore the cancel event that we just sent.
6078                mTouchEventQueue.ignoreCurrentlyMissingEvents();
6079            }
6080        }
6081    }
6082
6083    private void startTouch(float x, float y, long eventTime) {
6084        // Remember where the motion event started
6085        mLastTouchX = Math.round(x);
6086        mLastTouchY = Math.round(y);
6087        mLastTouchTime = eventTime;
6088        mVelocityTracker = VelocityTracker.obtain();
6089        mSnapScrollMode = SNAP_NONE;
6090    }
6091
6092    private void startDrag() {
6093        WebViewCore.reducePriority();
6094        // to get better performance, pause updating the picture
6095        WebViewCore.pauseUpdatePicture(mWebViewCore);
6096        if (!mDragFromTextInput) {
6097            nativeHideCursor();
6098        }
6099
6100        if (mHorizontalScrollBarMode != SCROLLBAR_ALWAYSOFF
6101                || mVerticalScrollBarMode != SCROLLBAR_ALWAYSOFF) {
6102            mZoomManager.invokeZoomPicker();
6103        }
6104    }
6105
6106    private void doDrag(int deltaX, int deltaY) {
6107        if ((deltaX | deltaY) != 0) {
6108            int oldX = mScrollX;
6109            int oldY = mScrollY;
6110            int rangeX = computeMaxScrollX();
6111            int rangeY = computeMaxScrollY();
6112            int overscrollDistance = mOverscrollDistance;
6113
6114            // Check for the original scrolling layer in case we change
6115            // directions.  mTouchMode might be TOUCH_DRAG_MODE if we have
6116            // reached the edge of a layer but mScrollingLayer will be non-zero
6117            // if we initiated the drag on a layer.
6118            if (mScrollingLayer != 0) {
6119                final int contentX = viewToContentDimension(deltaX);
6120                final int contentY = viewToContentDimension(deltaY);
6121
6122                // Check the scrolling bounds to see if we will actually do any
6123                // scrolling.  The rectangle is in document coordinates.
6124                final int maxX = mScrollingLayerRect.right;
6125                final int maxY = mScrollingLayerRect.bottom;
6126                final int resultX = Math.max(0,
6127                        Math.min(mScrollingLayerRect.left + contentX, maxX));
6128                final int resultY = Math.max(0,
6129                        Math.min(mScrollingLayerRect.top + contentY, maxY));
6130
6131                if (resultX != mScrollingLayerRect.left ||
6132                        resultY != mScrollingLayerRect.top) {
6133                    // In case we switched to dragging the page.
6134                    mTouchMode = TOUCH_DRAG_LAYER_MODE;
6135                    deltaX = contentX;
6136                    deltaY = contentY;
6137                    oldX = mScrollingLayerRect.left;
6138                    oldY = mScrollingLayerRect.top;
6139                    rangeX = maxX;
6140                    rangeY = maxY;
6141                } else {
6142                    // Scroll the main page if we are not going to scroll the
6143                    // layer.  This does not reset mScrollingLayer in case the
6144                    // user changes directions and the layer can scroll the
6145                    // other way.
6146                    mTouchMode = TOUCH_DRAG_MODE;
6147                }
6148            }
6149
6150            if (mOverScrollGlow != null) {
6151                mOverScrollGlow.setOverScrollDeltas(deltaX, deltaY);
6152            }
6153
6154            overScrollBy(deltaX, deltaY, oldX, oldY,
6155                    rangeX, rangeY,
6156                    mOverscrollDistance, mOverscrollDistance, true);
6157            if (mOverScrollGlow != null && mOverScrollGlow.isAnimating()) {
6158                invalidate();
6159            }
6160        }
6161        mZoomManager.keepZoomPickerVisible();
6162    }
6163
6164    private void stopTouch() {
6165        // we also use mVelocityTracker == null to tell us that we are
6166        // not "moving around", so we can take the slower/prettier
6167        // mode in the drawing code
6168        if (mVelocityTracker != null) {
6169            mVelocityTracker.recycle();
6170            mVelocityTracker = null;
6171        }
6172
6173        // Release any pulled glows
6174        if (mOverScrollGlow != null) {
6175            mOverScrollGlow.releaseAll();
6176        }
6177    }
6178
6179    private void cancelTouch() {
6180        // we also use mVelocityTracker == null to tell us that we are
6181        // not "moving around", so we can take the slower/prettier
6182        // mode in the drawing code
6183        if (mVelocityTracker != null) {
6184            mVelocityTracker.recycle();
6185            mVelocityTracker = null;
6186        }
6187
6188        if ((mTouchMode == TOUCH_DRAG_MODE
6189                || mTouchMode == TOUCH_DRAG_LAYER_MODE) && !mSelectingText) {
6190            WebViewCore.resumePriority();
6191            WebViewCore.resumeUpdatePicture(mWebViewCore);
6192        }
6193        mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
6194        mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
6195        mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
6196        mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
6197        if (getSettings().supportTouchOnly()) {
6198            removeTouchHighlight(true);
6199        }
6200        mHeldMotionless = MOTIONLESS_TRUE;
6201        mTouchMode = TOUCH_DONE_MODE;
6202        nativeHideCursor();
6203    }
6204
6205    @Override
6206    public boolean onGenericMotionEvent(MotionEvent event) {
6207        if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
6208            switch (event.getAction()) {
6209                case MotionEvent.ACTION_SCROLL: {
6210                    final float vscroll;
6211                    final float hscroll;
6212                    if ((event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0) {
6213                        vscroll = 0;
6214                        hscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
6215                    } else {
6216                        vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL);
6217                        hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
6218                    }
6219                    if (hscroll != 0 || vscroll != 0) {
6220                        final int vdelta = (int) (vscroll * getVerticalScrollFactor());
6221                        final int hdelta = (int) (hscroll * getHorizontalScrollFactor());
6222                        if (pinScrollBy(hdelta, vdelta, true, 0)) {
6223                            return true;
6224                        }
6225                    }
6226                }
6227            }
6228        }
6229        return super.onGenericMotionEvent(event);
6230    }
6231
6232    private long mTrackballFirstTime = 0;
6233    private long mTrackballLastTime = 0;
6234    private float mTrackballRemainsX = 0.0f;
6235    private float mTrackballRemainsY = 0.0f;
6236    private int mTrackballXMove = 0;
6237    private int mTrackballYMove = 0;
6238    private boolean mSelectingText = false;
6239    private boolean mSelectionStarted = false;
6240    private boolean mExtendSelection = false;
6241    private boolean mDrawSelectionPointer = false;
6242    private static final int TRACKBALL_KEY_TIMEOUT = 1000;
6243    private static final int TRACKBALL_TIMEOUT = 200;
6244    private static final int TRACKBALL_WAIT = 100;
6245    private static final int TRACKBALL_SCALE = 400;
6246    private static final int TRACKBALL_SCROLL_COUNT = 5;
6247    private static final int TRACKBALL_MOVE_COUNT = 10;
6248    private static final int TRACKBALL_MULTIPLIER = 3;
6249    private static final int SELECT_CURSOR_OFFSET = 16;
6250    private static final int SELECT_SCROLL = 5;
6251    private int mSelectX = 0;
6252    private int mSelectY = 0;
6253    private boolean mFocusSizeChanged = false;
6254    private boolean mTrackballDown = false;
6255    private long mTrackballUpTime = 0;
6256    private long mLastCursorTime = 0;
6257    private Rect mLastCursorBounds;
6258
6259    // Set by default; BrowserActivity clears to interpret trackball data
6260    // directly for movement. Currently, the framework only passes
6261    // arrow key events, not trackball events, from one child to the next
6262    private boolean mMapTrackballToArrowKeys = true;
6263
6264    public void setMapTrackballToArrowKeys(boolean setMap) {
6265        mMapTrackballToArrowKeys = setMap;
6266    }
6267
6268    void resetTrackballTime() {
6269        mTrackballLastTime = 0;
6270    }
6271
6272    @Override
6273    public boolean onTrackballEvent(MotionEvent ev) {
6274        long time = ev.getEventTime();
6275        if ((ev.getMetaState() & KeyEvent.META_ALT_ON) != 0) {
6276            if (ev.getY() > 0) pageDown(true);
6277            if (ev.getY() < 0) pageUp(true);
6278            return true;
6279        }
6280        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
6281            if (mSelectingText) {
6282                return true; // discard press if copy in progress
6283            }
6284            mTrackballDown = true;
6285            if (mNativeClass == 0) {
6286                return false;
6287            }
6288            nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true);
6289            if (time - mLastCursorTime <= TRACKBALL_TIMEOUT
6290                    && !mLastCursorBounds.equals(nativeGetCursorRingBounds())) {
6291                nativeSelectBestAt(mLastCursorBounds);
6292            }
6293            if (DebugFlags.WEB_VIEW) {
6294                Log.v(LOGTAG, "onTrackballEvent down ev=" + ev
6295                        + " time=" + time
6296                        + " mLastCursorTime=" + mLastCursorTime);
6297            }
6298            if (isInTouchMode()) requestFocusFromTouch();
6299            return false; // let common code in onKeyDown at it
6300        }
6301        if (ev.getAction() == MotionEvent.ACTION_UP) {
6302            // LONG_PRESS_CENTER is set in common onKeyDown
6303            mPrivateHandler.removeMessages(LONG_PRESS_CENTER);
6304            mTrackballDown = false;
6305            mTrackballUpTime = time;
6306            if (mSelectingText) {
6307                if (mExtendSelection) {
6308                    copySelection();
6309                    selectionDone();
6310                } else {
6311                    mExtendSelection = true;
6312                    nativeSetExtendSelection();
6313                    invalidate(); // draw the i-beam instead of the arrow
6314                }
6315                return true; // discard press if copy in progress
6316            }
6317            if (DebugFlags.WEB_VIEW) {
6318                Log.v(LOGTAG, "onTrackballEvent up ev=" + ev
6319                        + " time=" + time
6320                );
6321            }
6322            return false; // let common code in onKeyUp at it
6323        }
6324        if ((mMapTrackballToArrowKeys && (ev.getMetaState() & KeyEvent.META_SHIFT_ON) == 0) ||
6325                AccessibilityManager.getInstance(mContext).isEnabled()) {
6326            if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent gmail quit");
6327            return false;
6328        }
6329        if (mTrackballDown) {
6330            if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent down quit");
6331            return true; // discard move if trackball is down
6332        }
6333        if (time - mTrackballUpTime < TRACKBALL_TIMEOUT) {
6334            if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent up timeout quit");
6335            return true;
6336        }
6337        // TODO: alternatively we can do panning as touch does
6338        switchOutDrawHistory();
6339        if (time - mTrackballLastTime > TRACKBALL_TIMEOUT) {
6340            if (DebugFlags.WEB_VIEW) {
6341                Log.v(LOGTAG, "onTrackballEvent time="
6342                        + time + " last=" + mTrackballLastTime);
6343            }
6344            mTrackballFirstTime = time;
6345            mTrackballXMove = mTrackballYMove = 0;
6346        }
6347        mTrackballLastTime = time;
6348        if (DebugFlags.WEB_VIEW) {
6349            Log.v(LOGTAG, "onTrackballEvent ev=" + ev + " time=" + time);
6350        }
6351        mTrackballRemainsX += ev.getX();
6352        mTrackballRemainsY += ev.getY();
6353        doTrackball(time, ev.getMetaState());
6354        return true;
6355    }
6356
6357    void moveSelection(float xRate, float yRate) {
6358        if (mNativeClass == 0)
6359            return;
6360        int width = getViewWidth();
6361        int height = getViewHeight();
6362        mSelectX += xRate;
6363        mSelectY += yRate;
6364        int maxX = width + mScrollX;
6365        int maxY = height + mScrollY;
6366        mSelectX = Math.min(maxX, Math.max(mScrollX - SELECT_CURSOR_OFFSET
6367                , mSelectX));
6368        mSelectY = Math.min(maxY, Math.max(mScrollY - SELECT_CURSOR_OFFSET
6369                , mSelectY));
6370        if (DebugFlags.WEB_VIEW) {
6371            Log.v(LOGTAG, "moveSelection"
6372                    + " mSelectX=" + mSelectX
6373                    + " mSelectY=" + mSelectY
6374                    + " mScrollX=" + mScrollX
6375                    + " mScrollY=" + mScrollY
6376                    + " xRate=" + xRate
6377                    + " yRate=" + yRate
6378                    );
6379        }
6380        nativeMoveSelection(viewToContentX(mSelectX), viewToContentY(mSelectY));
6381        int scrollX = mSelectX < mScrollX ? -SELECT_CURSOR_OFFSET
6382                : mSelectX > maxX - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET
6383                : 0;
6384        int scrollY = mSelectY < mScrollY ? -SELECT_CURSOR_OFFSET
6385                : mSelectY > maxY - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET
6386                : 0;
6387        pinScrollBy(scrollX, scrollY, true, 0);
6388        Rect select = new Rect(mSelectX, mSelectY, mSelectX + 1, mSelectY + 1);
6389        requestRectangleOnScreen(select);
6390        invalidate();
6391   }
6392
6393    private int scaleTrackballX(float xRate, int width) {
6394        int xMove = (int) (xRate / TRACKBALL_SCALE * width);
6395        int nextXMove = xMove;
6396        if (xMove > 0) {
6397            if (xMove > mTrackballXMove) {
6398                xMove -= mTrackballXMove;
6399            }
6400        } else if (xMove < mTrackballXMove) {
6401            xMove -= mTrackballXMove;
6402        }
6403        mTrackballXMove = nextXMove;
6404        return xMove;
6405    }
6406
6407    private int scaleTrackballY(float yRate, int height) {
6408        int yMove = (int) (yRate / TRACKBALL_SCALE * height);
6409        int nextYMove = yMove;
6410        if (yMove > 0) {
6411            if (yMove > mTrackballYMove) {
6412                yMove -= mTrackballYMove;
6413            }
6414        } else if (yMove < mTrackballYMove) {
6415            yMove -= mTrackballYMove;
6416        }
6417        mTrackballYMove = nextYMove;
6418        return yMove;
6419    }
6420
6421    private int keyCodeToSoundsEffect(int keyCode) {
6422        switch(keyCode) {
6423            case KeyEvent.KEYCODE_DPAD_UP:
6424                return SoundEffectConstants.NAVIGATION_UP;
6425            case KeyEvent.KEYCODE_DPAD_RIGHT:
6426                return SoundEffectConstants.NAVIGATION_RIGHT;
6427            case KeyEvent.KEYCODE_DPAD_DOWN:
6428                return SoundEffectConstants.NAVIGATION_DOWN;
6429            case KeyEvent.KEYCODE_DPAD_LEFT:
6430                return SoundEffectConstants.NAVIGATION_LEFT;
6431        }
6432        throw new IllegalArgumentException("keyCode must be one of " +
6433                "{KEYCODE_DPAD_UP, KEYCODE_DPAD_RIGHT, KEYCODE_DPAD_DOWN, " +
6434                "KEYCODE_DPAD_LEFT}.");
6435    }
6436
6437    private void doTrackball(long time, int metaState) {
6438        int elapsed = (int) (mTrackballLastTime - mTrackballFirstTime);
6439        if (elapsed == 0) {
6440            elapsed = TRACKBALL_TIMEOUT;
6441        }
6442        float xRate = mTrackballRemainsX * 1000 / elapsed;
6443        float yRate = mTrackballRemainsY * 1000 / elapsed;
6444        int viewWidth = getViewWidth();
6445        int viewHeight = getViewHeight();
6446        if (mSelectingText) {
6447            if (!mDrawSelectionPointer) {
6448                // The last selection was made by touch, disabling drawing the
6449                // selection pointer. Allow the trackball to adjust the
6450                // position of the touch control.
6451                mSelectX = contentToViewX(nativeSelectionX());
6452                mSelectY = contentToViewY(nativeSelectionY());
6453                mDrawSelectionPointer = mExtendSelection = true;
6454                nativeSetExtendSelection();
6455            }
6456            moveSelection(scaleTrackballX(xRate, viewWidth),
6457                    scaleTrackballY(yRate, viewHeight));
6458            mTrackballRemainsX = mTrackballRemainsY = 0;
6459            return;
6460        }
6461        float ax = Math.abs(xRate);
6462        float ay = Math.abs(yRate);
6463        float maxA = Math.max(ax, ay);
6464        if (DebugFlags.WEB_VIEW) {
6465            Log.v(LOGTAG, "doTrackball elapsed=" + elapsed
6466                    + " xRate=" + xRate
6467                    + " yRate=" + yRate
6468                    + " mTrackballRemainsX=" + mTrackballRemainsX
6469                    + " mTrackballRemainsY=" + mTrackballRemainsY);
6470        }
6471        int width = mContentWidth - viewWidth;
6472        int height = mContentHeight - viewHeight;
6473        if (width < 0) width = 0;
6474        if (height < 0) height = 0;
6475        ax = Math.abs(mTrackballRemainsX * TRACKBALL_MULTIPLIER);
6476        ay = Math.abs(mTrackballRemainsY * TRACKBALL_MULTIPLIER);
6477        maxA = Math.max(ax, ay);
6478        int count = Math.max(0, (int) maxA);
6479        int oldScrollX = mScrollX;
6480        int oldScrollY = mScrollY;
6481        if (count > 0) {
6482            int selectKeyCode = ax < ay ? mTrackballRemainsY < 0 ?
6483                    KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_DPAD_DOWN :
6484                    mTrackballRemainsX < 0 ? KeyEvent.KEYCODE_DPAD_LEFT :
6485                    KeyEvent.KEYCODE_DPAD_RIGHT;
6486            count = Math.min(count, TRACKBALL_MOVE_COUNT);
6487            if (DebugFlags.WEB_VIEW) {
6488                Log.v(LOGTAG, "doTrackball keyCode=" + selectKeyCode
6489                        + " count=" + count
6490                        + " mTrackballRemainsX=" + mTrackballRemainsX
6491                        + " mTrackballRemainsY=" + mTrackballRemainsY);
6492            }
6493            if (mNativeClass != 0 && nativePageShouldHandleShiftAndArrows()) {
6494                for (int i = 0; i < count; i++) {
6495                    letPageHandleNavKey(selectKeyCode, time, true, metaState);
6496                }
6497                letPageHandleNavKey(selectKeyCode, time, false, metaState);
6498            } else if (navHandledKey(selectKeyCode, count, false, time)) {
6499                playSoundEffect(keyCodeToSoundsEffect(selectKeyCode));
6500            }
6501            mTrackballRemainsX = mTrackballRemainsY = 0;
6502        }
6503        if (count >= TRACKBALL_SCROLL_COUNT) {
6504            int xMove = scaleTrackballX(xRate, width);
6505            int yMove = scaleTrackballY(yRate, height);
6506            if (DebugFlags.WEB_VIEW) {
6507                Log.v(LOGTAG, "doTrackball pinScrollBy"
6508                        + " count=" + count
6509                        + " xMove=" + xMove + " yMove=" + yMove
6510                        + " mScrollX-oldScrollX=" + (mScrollX-oldScrollX)
6511                        + " mScrollY-oldScrollY=" + (mScrollY-oldScrollY)
6512                        );
6513            }
6514            if (Math.abs(mScrollX - oldScrollX) > Math.abs(xMove)) {
6515                xMove = 0;
6516            }
6517            if (Math.abs(mScrollY - oldScrollY) > Math.abs(yMove)) {
6518                yMove = 0;
6519            }
6520            if (xMove != 0 || yMove != 0) {
6521                pinScrollBy(xMove, yMove, true, 0);
6522            }
6523        }
6524    }
6525
6526    /**
6527     * Compute the maximum horizontal scroll position. Used by {@link OverScrollGlow}.
6528     * @return Maximum horizontal scroll position within real content
6529     */
6530    int computeMaxScrollX() {
6531        return Math.max(computeRealHorizontalScrollRange() - getViewWidth(), 0);
6532    }
6533
6534    /**
6535     * Compute the maximum vertical scroll position. Used by {@link OverScrollGlow}.
6536     * @return Maximum vertical scroll position within real content
6537     */
6538    int computeMaxScrollY() {
6539        return Math.max(computeRealVerticalScrollRange() + getTitleHeight()
6540                - getViewHeightWithTitle(), 0);
6541    }
6542
6543    boolean updateScrollCoordinates(int x, int y) {
6544        int oldX = mScrollX;
6545        int oldY = mScrollY;
6546        mScrollX = x;
6547        mScrollY = y;
6548        if (oldX != mScrollX || oldY != mScrollY) {
6549            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
6550            return true;
6551        } else {
6552            return false;
6553        }
6554    }
6555
6556    public void flingScroll(int vx, int vy) {
6557        mScroller.fling(mScrollX, mScrollY, vx, vy, 0, computeMaxScrollX(), 0,
6558                computeMaxScrollY(), mOverflingDistance, mOverflingDistance);
6559        invalidate();
6560    }
6561
6562    private void doFling() {
6563        if (mVelocityTracker == null) {
6564            return;
6565        }
6566        int maxX = computeMaxScrollX();
6567        int maxY = computeMaxScrollY();
6568
6569        mVelocityTracker.computeCurrentVelocity(1000, mMaximumFling);
6570        int vx = (int) mVelocityTracker.getXVelocity();
6571        int vy = (int) mVelocityTracker.getYVelocity();
6572
6573        int scrollX = mScrollX;
6574        int scrollY = mScrollY;
6575        int overscrollDistance = mOverscrollDistance;
6576        int overflingDistance = mOverflingDistance;
6577
6578        // Use the layer's scroll data if applicable.
6579        if (mTouchMode == TOUCH_DRAG_LAYER_MODE) {
6580            scrollX = mScrollingLayerRect.left;
6581            scrollY = mScrollingLayerRect.top;
6582            maxX = mScrollingLayerRect.right;
6583            maxY = mScrollingLayerRect.bottom;
6584            // No overscrolling for layers.
6585            overscrollDistance = overflingDistance = 0;
6586        }
6587
6588        if (mSnapScrollMode != SNAP_NONE) {
6589            if ((mSnapScrollMode & SNAP_X) == SNAP_X) {
6590                vy = 0;
6591            } else {
6592                vx = 0;
6593            }
6594        }
6595        if (true /* EMG release: make our fling more like Maps' */) {
6596            // maps cuts their velocity in half
6597            vx = vx * 3 / 4;
6598            vy = vy * 3 / 4;
6599        }
6600        if ((maxX == 0 && vy == 0) || (maxY == 0 && vx == 0)) {
6601            WebViewCore.resumePriority();
6602            if (!mSelectingText) {
6603                WebViewCore.resumeUpdatePicture(mWebViewCore);
6604            }
6605            if (mScroller.springBack(scrollX, scrollY, 0, maxX, 0, maxY)) {
6606                invalidate();
6607            }
6608            return;
6609        }
6610        float currentVelocity = mScroller.getCurrVelocity();
6611        float velocity = (float) Math.hypot(vx, vy);
6612        if (mLastVelocity > 0 && currentVelocity > 0 && velocity
6613                > mLastVelocity * MINIMUM_VELOCITY_RATIO_FOR_ACCELERATION) {
6614            float deltaR = (float) (Math.abs(Math.atan2(mLastVelY, mLastVelX)
6615                    - Math.atan2(vy, vx)));
6616            final float circle = (float) (Math.PI) * 2.0f;
6617            if (deltaR > circle * 0.9f || deltaR < circle * 0.1f) {
6618                vx += currentVelocity * mLastVelX / mLastVelocity;
6619                vy += currentVelocity * mLastVelY / mLastVelocity;
6620                velocity = (float) Math.hypot(vx, vy);
6621                if (DebugFlags.WEB_VIEW) {
6622                    Log.v(LOGTAG, "doFling vx= " + vx + " vy=" + vy);
6623                }
6624            } else if (DebugFlags.WEB_VIEW) {
6625                Log.v(LOGTAG, "doFling missed " + deltaR / circle);
6626            }
6627        } else if (DebugFlags.WEB_VIEW) {
6628            Log.v(LOGTAG, "doFling start last=" + mLastVelocity
6629                    + " current=" + currentVelocity
6630                    + " vx=" + vx + " vy=" + vy
6631                    + " maxX=" + maxX + " maxY=" + maxY
6632                    + " scrollX=" + scrollX + " scrollY=" + scrollY
6633                    + " layer=" + mScrollingLayer);
6634        }
6635
6636        // Allow sloppy flings without overscrolling at the edges.
6637        if ((scrollX == 0 || scrollX == maxX) && Math.abs(vx) < Math.abs(vy)) {
6638            vx = 0;
6639        }
6640        if ((scrollY == 0 || scrollY == maxY) && Math.abs(vy) < Math.abs(vx)) {
6641            vy = 0;
6642        }
6643
6644        if (overscrollDistance < overflingDistance) {
6645            if ((vx > 0 && scrollX == -overscrollDistance) ||
6646                    (vx < 0 && scrollX == maxX + overscrollDistance)) {
6647                vx = 0;
6648            }
6649            if ((vy > 0 && scrollY == -overscrollDistance) ||
6650                    (vy < 0 && scrollY == maxY + overscrollDistance)) {
6651                vy = 0;
6652            }
6653        }
6654
6655        mLastVelX = vx;
6656        mLastVelY = vy;
6657        mLastVelocity = velocity;
6658
6659        // no horizontal overscroll if the content just fits
6660        mScroller.fling(scrollX, scrollY, -vx, -vy, 0, maxX, 0, maxY,
6661                maxX == 0 ? 0 : overflingDistance, overflingDistance);
6662        // Duration is calculated based on velocity. With range boundaries and overscroll
6663        // we may not know how long the final animation will take. (Hence the deprecation
6664        // warning on the call below.) It's not a big deal for scroll bars but if webcore
6665        // resumes during this effect we will take a performance hit. See computeScroll;
6666        // we resume webcore there when the animation is finished.
6667        final int time = mScroller.getDuration();
6668
6669        // Suppress scrollbars for layer scrolling.
6670        if (mTouchMode != TOUCH_DRAG_LAYER_MODE) {
6671            awakenScrollBars(time);
6672        }
6673
6674        invalidate();
6675    }
6676
6677    /**
6678     * Returns a view containing zoom controls i.e. +/- buttons. The caller is
6679     * in charge of installing this view to the view hierarchy. This view will
6680     * become visible when the user starts scrolling via touch and fade away if
6681     * the user does not interact with it.
6682     * <p/>
6683     * API version 3 introduces a built-in zoom mechanism that is shown
6684     * automatically by the MapView. This is the preferred approach for
6685     * showing the zoom UI.
6686     *
6687     * @deprecated The built-in zoom mechanism is preferred, see
6688     *             {@link WebSettings#setBuiltInZoomControls(boolean)}.
6689     */
6690    @Deprecated
6691    public View getZoomControls() {
6692        if (!getSettings().supportZoom()) {
6693            Log.w(LOGTAG, "This WebView doesn't support zoom.");
6694            return null;
6695        }
6696        return mZoomManager.getExternalZoomPicker();
6697    }
6698
6699    void dismissZoomControl() {
6700        mZoomManager.dismissZoomPicker();
6701    }
6702
6703    float getDefaultZoomScale() {
6704        return mZoomManager.getDefaultScale();
6705    }
6706
6707    /**
6708     * @return TRUE if the WebView can be zoomed in.
6709     */
6710    public boolean canZoomIn() {
6711        return mZoomManager.canZoomIn();
6712    }
6713
6714    /**
6715     * @return TRUE if the WebView can be zoomed out.
6716     */
6717    public boolean canZoomOut() {
6718        return mZoomManager.canZoomOut();
6719    }
6720
6721    /**
6722     * Perform zoom in in the webview
6723     * @return TRUE if zoom in succeeds. FALSE if no zoom changes.
6724     */
6725    public boolean zoomIn() {
6726        return mZoomManager.zoomIn();
6727    }
6728
6729    /**
6730     * Perform zoom out in the webview
6731     * @return TRUE if zoom out succeeds. FALSE if no zoom changes.
6732     */
6733    public boolean zoomOut() {
6734        return mZoomManager.zoomOut();
6735    }
6736
6737    private void updateSelection() {
6738        if (mNativeClass == 0) {
6739            return;
6740        }
6741        // mLastTouchX and mLastTouchY are the point in the current viewport
6742        int contentX = viewToContentX(mLastTouchX + mScrollX);
6743        int contentY = viewToContentY(mLastTouchY + mScrollY);
6744        int slop = viewToContentDimension(mNavSlop);
6745        Rect rect = new Rect(contentX - slop, contentY - slop,
6746                contentX + slop, contentY + slop);
6747        nativeSelectBestAt(rect);
6748        mInitialHitTestResult = hitTestResult(null);
6749    }
6750
6751    /**
6752     * Scroll the focused text field to match the WebTextView
6753     * @param xPercent New x position of the WebTextView from 0 to 1.
6754     */
6755    /*package*/ void scrollFocusedTextInputX(float xPercent) {
6756        if (!inEditingMode() || mWebViewCore == null) {
6757            return;
6758        }
6759        mWebViewCore.sendMessage(EventHub.SCROLL_TEXT_INPUT, 0,
6760                new Float(xPercent));
6761    }
6762
6763    /**
6764     * Scroll the focused textarea vertically to match the WebTextView
6765     * @param y New y position of the WebTextView in view coordinates
6766     */
6767    /* package */ void scrollFocusedTextInputY(int y) {
6768        if (!inEditingMode() || mWebViewCore == null) {
6769            return;
6770        }
6771        mWebViewCore.sendMessage(EventHub.SCROLL_TEXT_INPUT, 0, viewToContentDimension(y));
6772    }
6773
6774    /**
6775     * Set our starting point and time for a drag from the WebTextView.
6776     */
6777    /*package*/ void initiateTextFieldDrag(float x, float y, long eventTime) {
6778        if (!inEditingMode()) {
6779            return;
6780        }
6781        mLastTouchX = Math.round(x + mWebTextView.getLeft() - mScrollX);
6782        mLastTouchY = Math.round(y + mWebTextView.getTop() - mScrollY);
6783        mLastTouchTime = eventTime;
6784        if (!mScroller.isFinished()) {
6785            abortAnimation();
6786            mPrivateHandler.removeMessages(RESUME_WEBCORE_PRIORITY);
6787        }
6788        mSnapScrollMode = SNAP_NONE;
6789        mVelocityTracker = VelocityTracker.obtain();
6790        mTouchMode = TOUCH_DRAG_START_MODE;
6791    }
6792
6793    /**
6794     * Given a motion event from the WebTextView, set its location to our
6795     * coordinates, and handle the event.
6796     */
6797    /*package*/ boolean textFieldDrag(MotionEvent event) {
6798        if (!inEditingMode()) {
6799            return false;
6800        }
6801        mDragFromTextInput = true;
6802        event.offsetLocation((float) (mWebTextView.getLeft() - mScrollX),
6803                (float) (mWebTextView.getTop() - mScrollY));
6804        boolean result = onTouchEvent(event);
6805        mDragFromTextInput = false;
6806        return result;
6807    }
6808
6809    /**
6810     * Due a touch up from a WebTextView.  This will be handled by webkit to
6811     * change the selection.
6812     * @param event MotionEvent in the WebTextView's coordinates.
6813     */
6814    /*package*/ void touchUpOnTextField(MotionEvent event) {
6815        if (!inEditingMode()) {
6816            return;
6817        }
6818        int x = viewToContentX((int) event.getX() + mWebTextView.getLeft());
6819        int y = viewToContentY((int) event.getY() + mWebTextView.getTop());
6820        int slop = viewToContentDimension(mNavSlop);
6821        nativeMotionUp(x, y, slop);
6822    }
6823
6824    /**
6825     * Called when pressing the center key or trackball on a textfield.
6826     */
6827    /*package*/ void centerKeyPressOnTextField() {
6828        mWebViewCore.sendMessage(EventHub.CLICK, nativeCursorFramePointer(),
6829                    nativeCursorNodePointer());
6830    }
6831
6832    private void doShortPress() {
6833        if (mNativeClass == 0) {
6834            return;
6835        }
6836        if (mPreventDefault == PREVENT_DEFAULT_YES) {
6837            return;
6838        }
6839        mTouchMode = TOUCH_DONE_MODE;
6840        switchOutDrawHistory();
6841        // mLastTouchX and mLastTouchY are the point in the current viewport
6842        int contentX = viewToContentX(mLastTouchX + mScrollX);
6843        int contentY = viewToContentY(mLastTouchY + mScrollY);
6844        int slop = viewToContentDimension(mNavSlop);
6845        if (getSettings().supportTouchOnly()) {
6846            removeTouchHighlight(false);
6847            WebViewCore.TouchUpData touchUpData = new WebViewCore.TouchUpData();
6848            // use "0" as generation id to inform WebKit to use the same x/y as
6849            // it used when processing GET_TOUCH_HIGHLIGHT_RECTS
6850            touchUpData.mMoveGeneration = 0;
6851            mWebViewCore.sendMessage(EventHub.TOUCH_UP, touchUpData);
6852        } else if (nativePointInNavCache(contentX, contentY, slop)) {
6853            WebViewCore.MotionUpData motionUpData = new WebViewCore
6854                    .MotionUpData();
6855            motionUpData.mFrame = nativeCacheHitFramePointer();
6856            motionUpData.mNode = nativeCacheHitNodePointer();
6857            motionUpData.mBounds = nativeCacheHitNodeBounds();
6858            motionUpData.mX = contentX;
6859            motionUpData.mY = contentY;
6860            mWebViewCore.sendMessageAtFrontOfQueue(EventHub.VALID_NODE_BOUNDS,
6861                    motionUpData);
6862        } else {
6863            doMotionUp(contentX, contentY);
6864        }
6865    }
6866
6867    private void doMotionUp(int contentX, int contentY) {
6868        int slop = viewToContentDimension(mNavSlop);
6869        if (nativeMotionUp(contentX, contentY, slop) && mLogEvent) {
6870            EventLog.writeEvent(EventLogTags.BROWSER_SNAP_CENTER);
6871        }
6872        if (nativeHasCursorNode() && !nativeCursorIsTextInput()) {
6873            playSoundEffect(SoundEffectConstants.CLICK);
6874        }
6875    }
6876
6877    /**
6878     * Returns plugin bounds if x/y in content coordinates corresponds to a
6879     * plugin. Otherwise a NULL rectangle is returned.
6880     */
6881    Rect getPluginBounds(int x, int y) {
6882        int slop = viewToContentDimension(mNavSlop);
6883        if (nativePointInNavCache(x, y, slop) && nativeCacheHitIsPlugin()) {
6884            return nativeCacheHitNodeBounds();
6885        } else {
6886            return null;
6887        }
6888    }
6889
6890    /*
6891     * Return true if the rect (e.g. plugin) is fully visible and maximized
6892     * inside the WebView.
6893     */
6894    boolean isRectFitOnScreen(Rect rect) {
6895        final int rectWidth = rect.width();
6896        final int rectHeight = rect.height();
6897        final int viewWidth = getViewWidth();
6898        final int viewHeight = getViewHeightWithTitle();
6899        float scale = Math.min((float) viewWidth / rectWidth, (float) viewHeight / rectHeight);
6900        scale = mZoomManager.computeScaleWithLimits(scale);
6901        return !mZoomManager.willScaleTriggerZoom(scale)
6902                && contentToViewX(rect.left) >= mScrollX
6903                && contentToViewX(rect.right) <= mScrollX + viewWidth
6904                && contentToViewY(rect.top) >= mScrollY
6905                && contentToViewY(rect.bottom) <= mScrollY + viewHeight;
6906    }
6907
6908    /*
6909     * Maximize and center the rectangle, specified in the document coordinate
6910     * space, inside the WebView. If the zoom doesn't need to be changed, do an
6911     * animated scroll to center it. If the zoom needs to be changed, find the
6912     * zoom center and do a smooth zoom transition. The rect is in document
6913     * coordinates
6914     */
6915    void centerFitRect(Rect rect) {
6916        final int rectWidth = rect.width();
6917        final int rectHeight = rect.height();
6918        final int viewWidth = getViewWidth();
6919        final int viewHeight = getViewHeightWithTitle();
6920        float scale = Math.min((float) viewWidth / rectWidth, (float) viewHeight
6921                / rectHeight);
6922        scale = mZoomManager.computeScaleWithLimits(scale);
6923        if (!mZoomManager.willScaleTriggerZoom(scale)) {
6924            pinScrollTo(contentToViewX(rect.left + rectWidth / 2) - viewWidth / 2,
6925                    contentToViewY(rect.top + rectHeight / 2) - viewHeight / 2,
6926                    true, 0);
6927        } else {
6928            float actualScale = mZoomManager.getScale();
6929            float oldScreenX = rect.left * actualScale - mScrollX;
6930            float rectViewX = rect.left * scale;
6931            float rectViewWidth = rectWidth * scale;
6932            float newMaxWidth = mContentWidth * scale;
6933            float newScreenX = (viewWidth - rectViewWidth) / 2;
6934            // pin the newX to the WebView
6935            if (newScreenX > rectViewX) {
6936                newScreenX = rectViewX;
6937            } else if (newScreenX > (newMaxWidth - rectViewX - rectViewWidth)) {
6938                newScreenX = viewWidth - (newMaxWidth - rectViewX);
6939            }
6940            float zoomCenterX = (oldScreenX * scale - newScreenX * actualScale)
6941                    / (scale - actualScale);
6942            float oldScreenY = rect.top * actualScale + getTitleHeight()
6943                    - mScrollY;
6944            float rectViewY = rect.top * scale + getTitleHeight();
6945            float rectViewHeight = rectHeight * scale;
6946            float newMaxHeight = mContentHeight * scale + getTitleHeight();
6947            float newScreenY = (viewHeight - rectViewHeight) / 2;
6948            // pin the newY to the WebView
6949            if (newScreenY > rectViewY) {
6950                newScreenY = rectViewY;
6951            } else if (newScreenY > (newMaxHeight - rectViewY - rectViewHeight)) {
6952                newScreenY = viewHeight - (newMaxHeight - rectViewY);
6953            }
6954            float zoomCenterY = (oldScreenY * scale - newScreenY * actualScale)
6955                    / (scale - actualScale);
6956            mZoomManager.setZoomCenter(zoomCenterX, zoomCenterY);
6957            mZoomManager.startZoomAnimation(scale, false);
6958        }
6959    }
6960
6961    // Called by JNI to handle a touch on a node representing an email address,
6962    // address, or phone number
6963    private void overrideLoading(String url) {
6964        mCallbackProxy.uiOverrideUrlLoading(url);
6965    }
6966
6967    @Override
6968    public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
6969        // FIXME: If a subwindow is showing find, and the user touches the
6970        // background window, it can steal focus.
6971        if (mFindIsUp) return false;
6972        boolean result = false;
6973        if (inEditingMode()) {
6974            result = mWebTextView.requestFocus(direction,
6975                    previouslyFocusedRect);
6976        } else {
6977            result = super.requestFocus(direction, previouslyFocusedRect);
6978            if (mWebViewCore.getSettings().getNeedInitialFocus() && !isInTouchMode()) {
6979                // For cases such as GMail, where we gain focus from a direction,
6980                // we want to move to the first available link.
6981                // FIXME: If there are no visible links, we may not want to
6982                int fakeKeyDirection = 0;
6983                switch(direction) {
6984                    case View.FOCUS_UP:
6985                        fakeKeyDirection = KeyEvent.KEYCODE_DPAD_UP;
6986                        break;
6987                    case View.FOCUS_DOWN:
6988                        fakeKeyDirection = KeyEvent.KEYCODE_DPAD_DOWN;
6989                        break;
6990                    case View.FOCUS_LEFT:
6991                        fakeKeyDirection = KeyEvent.KEYCODE_DPAD_LEFT;
6992                        break;
6993                    case View.FOCUS_RIGHT:
6994                        fakeKeyDirection = KeyEvent.KEYCODE_DPAD_RIGHT;
6995                        break;
6996                    default:
6997                        return result;
6998                }
6999                if (mNativeClass != 0 && !nativeHasCursorNode()) {
7000                    navHandledKey(fakeKeyDirection, 1, true, 0);
7001                }
7002            }
7003        }
7004        return result;
7005    }
7006
7007    @Override
7008    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
7009        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
7010
7011        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
7012        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
7013        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
7014        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
7015
7016        int measuredHeight = heightSize;
7017        int measuredWidth = widthSize;
7018
7019        // Grab the content size from WebViewCore.
7020        int contentHeight = contentToViewDimension(mContentHeight);
7021        int contentWidth = contentToViewDimension(mContentWidth);
7022
7023//        Log.d(LOGTAG, "------- measure " + heightMode);
7024
7025        if (heightMode != MeasureSpec.EXACTLY) {
7026            mHeightCanMeasure = true;
7027            measuredHeight = contentHeight;
7028            if (heightMode == MeasureSpec.AT_MOST) {
7029                // If we are larger than the AT_MOST height, then our height can
7030                // no longer be measured and we should scroll internally.
7031                if (measuredHeight > heightSize) {
7032                    measuredHeight = heightSize;
7033                    mHeightCanMeasure = false;
7034                } else if (measuredHeight < heightSize) {
7035                    measuredHeight |= MEASURED_STATE_TOO_SMALL;
7036                }
7037            }
7038        } else {
7039            mHeightCanMeasure = false;
7040        }
7041        if (mNativeClass != 0) {
7042            nativeSetHeightCanMeasure(mHeightCanMeasure);
7043        }
7044        // For the width, always use the given size unless unspecified.
7045        if (widthMode == MeasureSpec.UNSPECIFIED) {
7046            mWidthCanMeasure = true;
7047            measuredWidth = contentWidth;
7048        } else {
7049            if (measuredWidth < contentWidth) {
7050                measuredWidth |= MEASURED_STATE_TOO_SMALL;
7051            }
7052            mWidthCanMeasure = false;
7053        }
7054
7055        synchronized (this) {
7056            setMeasuredDimension(measuredWidth, measuredHeight);
7057        }
7058    }
7059
7060    @Override
7061    public boolean requestChildRectangleOnScreen(View child,
7062                                                 Rect rect,
7063                                                 boolean immediate) {
7064        if (mNativeClass == 0) {
7065            return false;
7066        }
7067        // don't scroll while in zoom animation. When it is done, we will adjust
7068        // the necessary components (e.g., WebTextView if it is in editing mode)
7069        if (mZoomManager.isFixedLengthAnimationInProgress()) {
7070            return false;
7071        }
7072
7073        rect.offset(child.getLeft() - child.getScrollX(),
7074                child.getTop() - child.getScrollY());
7075
7076        Rect content = new Rect(viewToContentX(mScrollX),
7077                viewToContentY(mScrollY),
7078                viewToContentX(mScrollX + getWidth()
7079                - getVerticalScrollbarWidth()),
7080                viewToContentY(mScrollY + getViewHeightWithTitle()));
7081        content = nativeSubtractLayers(content);
7082        int screenTop = contentToViewY(content.top);
7083        int screenBottom = contentToViewY(content.bottom);
7084        int height = screenBottom - screenTop;
7085        int scrollYDelta = 0;
7086
7087        if (rect.bottom > screenBottom) {
7088            int oneThirdOfScreenHeight = height / 3;
7089            if (rect.height() > 2 * oneThirdOfScreenHeight) {
7090                // If the rectangle is too tall to fit in the bottom two thirds
7091                // of the screen, place it at the top.
7092                scrollYDelta = rect.top - screenTop;
7093            } else {
7094                // If the rectangle will still fit on screen, we want its
7095                // top to be in the top third of the screen.
7096                scrollYDelta = rect.top - (screenTop + oneThirdOfScreenHeight);
7097            }
7098        } else if (rect.top < screenTop) {
7099            scrollYDelta = rect.top - screenTop;
7100        }
7101
7102        int screenLeft = contentToViewX(content.left);
7103        int screenRight = contentToViewX(content.right);
7104        int width = screenRight - screenLeft;
7105        int scrollXDelta = 0;
7106
7107        if (rect.right > screenRight && rect.left > screenLeft) {
7108            if (rect.width() > width) {
7109                scrollXDelta += (rect.left - screenLeft);
7110            } else {
7111                scrollXDelta += (rect.right - screenRight);
7112            }
7113        } else if (rect.left < screenLeft) {
7114            scrollXDelta -= (screenLeft - rect.left);
7115        }
7116
7117        if ((scrollYDelta | scrollXDelta) != 0) {
7118            return pinScrollBy(scrollXDelta, scrollYDelta, !immediate, 0);
7119        }
7120
7121        return false;
7122    }
7123
7124    /* package */ void replaceTextfieldText(int oldStart, int oldEnd,
7125            String replace, int newStart, int newEnd) {
7126        WebViewCore.ReplaceTextData arg = new WebViewCore.ReplaceTextData();
7127        arg.mReplace = replace;
7128        arg.mNewStart = newStart;
7129        arg.mNewEnd = newEnd;
7130        mTextGeneration++;
7131        arg.mTextGeneration = mTextGeneration;
7132        mWebViewCore.sendMessage(EventHub.REPLACE_TEXT, oldStart, oldEnd, arg);
7133    }
7134
7135    /* package */ void passToJavaScript(String currentText, KeyEvent event) {
7136        WebViewCore.JSKeyData arg = new WebViewCore.JSKeyData();
7137        arg.mEvent = event;
7138        arg.mCurrentText = currentText;
7139        // Increase our text generation number, and pass it to webcore thread
7140        mTextGeneration++;
7141        mWebViewCore.sendMessage(EventHub.PASS_TO_JS, mTextGeneration, 0, arg);
7142        // WebKit's document state is not saved until about to leave the page.
7143        // To make sure the host application, like Browser, has the up to date
7144        // document state when it goes to background, we force to save the
7145        // document state.
7146        mWebViewCore.removeMessages(EventHub.SAVE_DOCUMENT_STATE);
7147        mWebViewCore.sendMessageDelayed(EventHub.SAVE_DOCUMENT_STATE,
7148                cursorData(), 1000);
7149    }
7150
7151    /* package */ synchronized WebViewCore getWebViewCore() {
7152        return mWebViewCore;
7153    }
7154
7155    /**
7156     * Used only by TouchEventQueue to store pending touch events.
7157     */
7158    private static class QueuedTouch {
7159        long mSequence;
7160        MotionEvent mEvent; // Optional
7161        TouchEventData mTed; // Optional
7162
7163        QueuedTouch mNext;
7164
7165        public QueuedTouch set(TouchEventData ted) {
7166            mSequence = ted.mSequence;
7167            mTed = ted;
7168            mEvent = null;
7169            mNext = null;
7170            return this;
7171        }
7172
7173        public QueuedTouch set(MotionEvent ev, long sequence) {
7174            mEvent = MotionEvent.obtain(ev);
7175            mSequence = sequence;
7176            mTed = null;
7177            mNext = null;
7178            return this;
7179        }
7180
7181        public QueuedTouch add(QueuedTouch other) {
7182            if (other.mSequence < mSequence) {
7183                other.mNext = this;
7184                return other;
7185            }
7186
7187            QueuedTouch insertAt = this;
7188            while (insertAt.mNext != null && insertAt.mNext.mSequence < other.mSequence) {
7189                insertAt = insertAt.mNext;
7190            }
7191            other.mNext = insertAt.mNext;
7192            insertAt.mNext = other;
7193            return this;
7194        }
7195    }
7196
7197    /**
7198     * WebView handles touch events asynchronously since some events must be passed to WebKit
7199     * for potentially slower processing. TouchEventQueue serializes touch events regardless
7200     * of which path they take to ensure that no events are ever processed out of order
7201     * by WebView.
7202     */
7203    private class TouchEventQueue {
7204        private long mNextTouchSequence = Long.MIN_VALUE + 1;
7205        private long mLastHandledTouchSequence = Long.MIN_VALUE;
7206        private long mIgnoreUntilSequence = Long.MIN_VALUE + 1;
7207
7208        // Events waiting to be processed.
7209        private QueuedTouch mTouchEventQueue;
7210
7211        // Known events that are waiting on a response before being enqueued.
7212        private QueuedTouch mPreQueue;
7213
7214        // Pool of QueuedTouch objects saved for later use.
7215        private QueuedTouch mQueuedTouchRecycleBin;
7216        private int mQueuedTouchRecycleCount;
7217
7218        private long mLastEventTime = Long.MAX_VALUE;
7219        private static final int MAX_RECYCLED_QUEUED_TOUCH = 15;
7220
7221        // milliseconds until we abandon hope of getting all of a previous gesture
7222        private static final int QUEUED_GESTURE_TIMEOUT = 1000;
7223
7224        private QueuedTouch obtainQueuedTouch() {
7225            if (mQueuedTouchRecycleBin != null) {
7226                QueuedTouch result = mQueuedTouchRecycleBin;
7227                mQueuedTouchRecycleBin = result.mNext;
7228                mQueuedTouchRecycleCount--;
7229                return result;
7230            }
7231            return new QueuedTouch();
7232        }
7233
7234        /**
7235         * Allow events with any currently missing sequence numbers to be skipped in processing.
7236         */
7237        public void ignoreCurrentlyMissingEvents() {
7238            mIgnoreUntilSequence = mNextTouchSequence;
7239
7240            // Run any events we have available and complete, pre-queued or otherwise.
7241            runQueuedAndPreQueuedEvents();
7242        }
7243
7244        private void runQueuedAndPreQueuedEvents() {
7245            QueuedTouch qd = mPreQueue;
7246            boolean fromPreQueue = true;
7247            while (qd != null && qd.mSequence == mLastHandledTouchSequence + 1) {
7248                handleQueuedTouch(qd);
7249                QueuedTouch recycleMe = qd;
7250                if (fromPreQueue) {
7251                    mPreQueue = qd.mNext;
7252                } else {
7253                    mTouchEventQueue = qd.mNext;
7254                }
7255                recycleQueuedTouch(recycleMe);
7256                mLastHandledTouchSequence++;
7257
7258                long nextPre = mPreQueue != null ? mPreQueue.mSequence : Long.MAX_VALUE;
7259                long nextQueued = mTouchEventQueue != null ?
7260                        mTouchEventQueue.mSequence : Long.MAX_VALUE;
7261                fromPreQueue = nextPre < nextQueued;
7262                qd = fromPreQueue ? mPreQueue : mTouchEventQueue;
7263            }
7264        }
7265
7266        /**
7267         * Add a TouchEventData to the pre-queue.
7268         *
7269         * An event in the pre-queue is an event that we know about that
7270         * has been sent to webkit, but that we haven't received back and
7271         * enqueued into the normal touch queue yet. If webkit ever times
7272         * out and we need to ignore currently missing events, we'll run
7273         * events from the pre-queue to patch the holes.
7274         *
7275         * @param ted TouchEventData to pre-queue
7276         */
7277        public void preQueueTouchEventData(TouchEventData ted) {
7278            QueuedTouch newTouch = obtainQueuedTouch().set(ted);
7279            if (mPreQueue == null) {
7280                mPreQueue = newTouch;
7281            } else {
7282                QueuedTouch insertionPoint = mPreQueue;
7283                while (insertionPoint.mNext != null &&
7284                        insertionPoint.mNext.mSequence < newTouch.mSequence) {
7285                    insertionPoint = insertionPoint.mNext;
7286                }
7287                newTouch.mNext = insertionPoint.mNext;
7288                insertionPoint.mNext = newTouch;
7289            }
7290        }
7291
7292        private void recycleQueuedTouch(QueuedTouch qd) {
7293            if (mQueuedTouchRecycleCount < MAX_RECYCLED_QUEUED_TOUCH) {
7294                qd.mNext = mQueuedTouchRecycleBin;
7295                mQueuedTouchRecycleBin = qd;
7296                mQueuedTouchRecycleCount++;
7297            }
7298        }
7299
7300        /**
7301         * Reset the touch event queue. This will dump any pending events
7302         * and reset the sequence numbering.
7303         */
7304        public void reset() {
7305            mNextTouchSequence = Long.MIN_VALUE + 1;
7306            mLastHandledTouchSequence = Long.MIN_VALUE;
7307            mIgnoreUntilSequence = Long.MIN_VALUE + 1;
7308            while (mTouchEventQueue != null) {
7309                QueuedTouch recycleMe = mTouchEventQueue;
7310                mTouchEventQueue = mTouchEventQueue.mNext;
7311                recycleQueuedTouch(recycleMe);
7312            }
7313            while (mPreQueue != null) {
7314                QueuedTouch recycleMe = mPreQueue;
7315                mPreQueue = mPreQueue.mNext;
7316                recycleQueuedTouch(recycleMe);
7317            }
7318        }
7319
7320        /**
7321         * Return the next valid sequence number for tagging incoming touch events.
7322         * @return The next touch event sequence number
7323         */
7324        public long nextTouchSequence() {
7325            return mNextTouchSequence++;
7326        }
7327
7328        /**
7329         * Enqueue a touch event in the form of TouchEventData.
7330         * The sequence number will be read from the mSequence field of the argument.
7331         *
7332         * If the touch event's sequence number is the next in line to be processed, it will
7333         * be handled before this method returns. Any subsequent events that have already
7334         * been queued will also be processed in their proper order.
7335         *
7336         * @param ted Touch data to be processed in order.
7337         * @return true if the event was processed before returning, false if it was just enqueued.
7338         */
7339        public boolean enqueueTouchEvent(TouchEventData ted) {
7340            // Remove from the pre-queue if present
7341            QueuedTouch preQueue = mPreQueue;
7342            if (preQueue != null) {
7343                // On exiting this block, preQueue is set to the pre-queued QueuedTouch object
7344                // if it was present in the pre-queue, and removed from the pre-queue itself.
7345                if (preQueue.mSequence == ted.mSequence) {
7346                    mPreQueue = preQueue.mNext;
7347                } else {
7348                    QueuedTouch prev = preQueue;
7349                    preQueue = null;
7350                    while (prev.mNext != null) {
7351                        if (prev.mNext.mSequence == ted.mSequence) {
7352                            preQueue = prev.mNext;
7353                            prev.mNext = preQueue.mNext;
7354                            break;
7355                        } else {
7356                            prev = prev.mNext;
7357                        }
7358                    }
7359                }
7360            }
7361
7362            if (ted.mSequence < mLastHandledTouchSequence) {
7363                // Stale event and we already moved on; drop it. (Should not be common.)
7364                Log.w(LOGTAG, "Stale touch event " + MotionEvent.actionToString(ted.mAction) +
7365                        " received from webcore; ignoring");
7366                return false;
7367            }
7368
7369            if (dropStaleGestures(ted.mMotionEvent, ted.mSequence)) {
7370                return false;
7371            }
7372
7373            // dropStaleGestures above might have fast-forwarded us to
7374            // an event we have already.
7375            runNextQueuedEvents();
7376
7377            if (mLastHandledTouchSequence + 1 == ted.mSequence) {
7378                if (preQueue != null) {
7379                    recycleQueuedTouch(preQueue);
7380                    preQueue = null;
7381                }
7382                handleQueuedTouchEventData(ted);
7383
7384                mLastHandledTouchSequence++;
7385
7386                // Do we have any more? Run them if so.
7387                runNextQueuedEvents();
7388            } else {
7389                // Reuse the pre-queued object if we had it.
7390                QueuedTouch qd = preQueue != null ? preQueue : obtainQueuedTouch().set(ted);
7391                mTouchEventQueue = mTouchEventQueue == null ? qd : mTouchEventQueue.add(qd);
7392            }
7393            return true;
7394        }
7395
7396        /**
7397         * Enqueue a touch event in the form of a MotionEvent from the framework.
7398         *
7399         * If the touch event's sequence number is the next in line to be processed, it will
7400         * be handled before this method returns. Any subsequent events that have already
7401         * been queued will also be processed in their proper order.
7402         *
7403         * @param ev MotionEvent to be processed in order
7404         */
7405        public void enqueueTouchEvent(MotionEvent ev) {
7406            final long sequence = nextTouchSequence();
7407
7408            if (dropStaleGestures(ev, sequence)) {
7409                return;
7410            }
7411
7412            // dropStaleGestures above might have fast-forwarded us to
7413            // an event we have already.
7414            runNextQueuedEvents();
7415
7416            if (mLastHandledTouchSequence + 1 == sequence) {
7417                handleQueuedMotionEvent(ev);
7418
7419                mLastHandledTouchSequence++;
7420
7421                // Do we have any more? Run them if so.
7422                runNextQueuedEvents();
7423            } else {
7424                QueuedTouch qd = obtainQueuedTouch().set(ev, sequence);
7425                mTouchEventQueue = mTouchEventQueue == null ? qd : mTouchEventQueue.add(qd);
7426            }
7427        }
7428
7429        private void runNextQueuedEvents() {
7430            QueuedTouch qd = mTouchEventQueue;
7431            while (qd != null && qd.mSequence == mLastHandledTouchSequence + 1) {
7432                handleQueuedTouch(qd);
7433                QueuedTouch recycleMe = qd;
7434                qd = qd.mNext;
7435                recycleQueuedTouch(recycleMe);
7436                mLastHandledTouchSequence++;
7437            }
7438            mTouchEventQueue = qd;
7439        }
7440
7441        private boolean dropStaleGestures(MotionEvent ev, long sequence) {
7442            if (ev != null && ev.getAction() == MotionEvent.ACTION_MOVE && !mConfirmMove) {
7443                // This is to make sure that we don't attempt to process a tap
7444                // or long press when webkit takes too long to get back to us.
7445                // The movement will be properly confirmed when we process the
7446                // enqueued event later.
7447                final int dx = Math.round(ev.getX()) - mLastTouchX;
7448                final int dy = Math.round(ev.getY()) - mLastTouchY;
7449                if (dx * dx + dy * dy > mTouchSlopSquare) {
7450                    mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
7451                    mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
7452                }
7453            }
7454
7455            if (mTouchEventQueue == null) {
7456                return sequence <= mLastHandledTouchSequence;
7457            }
7458
7459            // If we have a new down event and it's been a while since the last event
7460            // we saw, catch up as best we can and keep going.
7461            if (ev != null && ev.getAction() == MotionEvent.ACTION_DOWN) {
7462                long eventTime = ev.getEventTime();
7463                long lastHandledEventTime = mLastEventTime;
7464                if (eventTime > lastHandledEventTime + QUEUED_GESTURE_TIMEOUT) {
7465                    Log.w(LOGTAG, "Got ACTION_DOWN but still waiting on stale event. " +
7466                            "Catching up.");
7467                    runQueuedAndPreQueuedEvents();
7468
7469                    // Drop leftovers that we truly don't have.
7470                    QueuedTouch qd = mTouchEventQueue;
7471                    while (qd != null && qd.mSequence < sequence) {
7472                        QueuedTouch recycleMe = qd;
7473                        qd = qd.mNext;
7474                        recycleQueuedTouch(recycleMe);
7475                    }
7476                    mTouchEventQueue = qd;
7477                    mLastHandledTouchSequence = sequence - 1;
7478                }
7479            }
7480
7481            if (mIgnoreUntilSequence - 1 > mLastHandledTouchSequence) {
7482                QueuedTouch qd = mTouchEventQueue;
7483                while (qd != null && qd.mSequence < mIgnoreUntilSequence) {
7484                    QueuedTouch recycleMe = qd;
7485                    qd = qd.mNext;
7486                    recycleQueuedTouch(recycleMe);
7487                }
7488                mTouchEventQueue = qd;
7489                mLastHandledTouchSequence = mIgnoreUntilSequence - 1;
7490            }
7491
7492            if (mPreQueue != null) {
7493                // Drop stale prequeued events
7494                QueuedTouch qd = mPreQueue;
7495                while (qd != null && qd.mSequence < mIgnoreUntilSequence) {
7496                    QueuedTouch recycleMe = qd;
7497                    qd = qd.mNext;
7498                    recycleQueuedTouch(recycleMe);
7499                }
7500                mPreQueue = qd;
7501            }
7502
7503            return sequence <= mLastHandledTouchSequence;
7504        }
7505
7506        private void handleQueuedTouch(QueuedTouch qt) {
7507            if (qt.mTed != null) {
7508                handleQueuedTouchEventData(qt.mTed);
7509            } else {
7510                handleQueuedMotionEvent(qt.mEvent);
7511                qt.mEvent.recycle();
7512            }
7513        }
7514
7515        private void handleQueuedMotionEvent(MotionEvent ev) {
7516            mLastEventTime = ev.getEventTime();
7517            int action = ev.getActionMasked();
7518            if (ev.getPointerCount() > 1) {  // Multi-touch
7519                handleMultiTouchInWebView(ev);
7520            } else {
7521                final ScaleGestureDetector detector = mZoomManager.getMultiTouchGestureDetector();
7522                if (detector != null && mPreventDefault != PREVENT_DEFAULT_YES) {
7523                    // ScaleGestureDetector needs a consistent event stream to operate properly.
7524                    // It won't take any action with fewer than two pointers, but it needs to
7525                    // update internal bookkeeping state.
7526                    detector.onTouchEvent(ev);
7527                }
7528
7529                handleTouchEventCommon(ev, action, Math.round(ev.getX()), Math.round(ev.getY()));
7530            }
7531        }
7532
7533        private void handleQueuedTouchEventData(TouchEventData ted) {
7534            if (ted.mMotionEvent != null) {
7535                mLastEventTime = ted.mMotionEvent.getEventTime();
7536            }
7537            if (!ted.mReprocess) {
7538                if (ted.mAction == MotionEvent.ACTION_DOWN
7539                        && mPreventDefault == PREVENT_DEFAULT_MAYBE_YES) {
7540                    // if prevent default is called from WebCore, UI
7541                    // will not handle the rest of the touch events any
7542                    // more.
7543                    mPreventDefault = ted.mNativeResult ? PREVENT_DEFAULT_YES
7544                            : PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN;
7545                } else if (ted.mAction == MotionEvent.ACTION_MOVE
7546                        && mPreventDefault == PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN) {
7547                    // the return for the first ACTION_MOVE will decide
7548                    // whether UI will handle touch or not. Currently no
7549                    // support for alternating prevent default
7550                    mPreventDefault = ted.mNativeResult ? PREVENT_DEFAULT_YES
7551                            : PREVENT_DEFAULT_NO;
7552                }
7553                if (mPreventDefault == PREVENT_DEFAULT_YES) {
7554                    mTouchHighlightRegion.setEmpty();
7555                }
7556            } else {
7557                if (ted.mPoints.length > 1) {  // multi-touch
7558                    if (!ted.mNativeResult && mPreventDefault != PREVENT_DEFAULT_YES) {
7559                        mPreventDefault = PREVENT_DEFAULT_NO;
7560                        handleMultiTouchInWebView(ted.mMotionEvent);
7561                    } else {
7562                        mPreventDefault = PREVENT_DEFAULT_YES;
7563                    }
7564                    return;
7565                }
7566
7567                // prevent default is not called in WebCore, so the
7568                // message needs to be reprocessed in UI
7569                if (!ted.mNativeResult) {
7570                    // Following is for single touch.
7571                    switch (ted.mAction) {
7572                        case MotionEvent.ACTION_DOWN:
7573                            mLastDeferTouchX = ted.mPointsInView[0].x;
7574                            mLastDeferTouchY = ted.mPointsInView[0].y;
7575                            mDeferTouchMode = TOUCH_INIT_MODE;
7576                            break;
7577                        case MotionEvent.ACTION_MOVE: {
7578                            // no snapping in defer process
7579                            int x = ted.mPointsInView[0].x;
7580                            int y = ted.mPointsInView[0].y;
7581
7582                            if (mDeferTouchMode != TOUCH_DRAG_MODE) {
7583                                mDeferTouchMode = TOUCH_DRAG_MODE;
7584                                mLastDeferTouchX = x;
7585                                mLastDeferTouchY = y;
7586                                startScrollingLayer(x, y);
7587                                startDrag();
7588                            }
7589                            int deltaX = pinLocX((int) (mScrollX
7590                                    + mLastDeferTouchX - x))
7591                                    - mScrollX;
7592                            int deltaY = pinLocY((int) (mScrollY
7593                                    + mLastDeferTouchY - y))
7594                                    - mScrollY;
7595                            doDrag(deltaX, deltaY);
7596                            if (deltaX != 0) mLastDeferTouchX = x;
7597                            if (deltaY != 0) mLastDeferTouchY = y;
7598                            break;
7599                        }
7600                        case MotionEvent.ACTION_UP:
7601                        case MotionEvent.ACTION_CANCEL:
7602                            if (mDeferTouchMode == TOUCH_DRAG_MODE) {
7603                                // no fling in defer process
7604                                mScroller.springBack(mScrollX, mScrollY, 0,
7605                                        computeMaxScrollX(), 0,
7606                                        computeMaxScrollY());
7607                                invalidate();
7608                                WebViewCore.resumePriority();
7609                                WebViewCore.resumeUpdatePicture(mWebViewCore);
7610                            }
7611                            mDeferTouchMode = TOUCH_DONE_MODE;
7612                            break;
7613                        case WebViewCore.ACTION_DOUBLETAP:
7614                            // doDoubleTap() needs mLastTouchX/Y as anchor
7615                            mLastDeferTouchX = ted.mPointsInView[0].x;
7616                            mLastDeferTouchY = ted.mPointsInView[0].y;
7617                            mZoomManager.handleDoubleTap(mLastTouchX, mLastTouchY);
7618                            mDeferTouchMode = TOUCH_DONE_MODE;
7619                            break;
7620                        case WebViewCore.ACTION_LONGPRESS:
7621                            HitTestResult hitTest = getHitTestResult();
7622                            if (hitTest != null && hitTest.mType
7623                                    != HitTestResult.UNKNOWN_TYPE) {
7624                                performLongClick();
7625                            }
7626                            mDeferTouchMode = TOUCH_DONE_MODE;
7627                            break;
7628                    }
7629                }
7630            }
7631        }
7632    }
7633
7634    //-------------------------------------------------------------------------
7635    // Methods can be called from a separate thread, like WebViewCore
7636    // If it needs to call the View system, it has to send message.
7637    //-------------------------------------------------------------------------
7638
7639    /**
7640     * General handler to receive message coming from webkit thread
7641     */
7642    class PrivateHandler extends Handler {
7643        @Override
7644        public void handleMessage(Message msg) {
7645            // exclude INVAL_RECT_MSG_ID since it is frequently output
7646            if (DebugFlags.WEB_VIEW && msg.what != INVAL_RECT_MSG_ID) {
7647                if (msg.what >= FIRST_PRIVATE_MSG_ID
7648                        && msg.what <= LAST_PRIVATE_MSG_ID) {
7649                    Log.v(LOGTAG, HandlerPrivateDebugString[msg.what
7650                            - FIRST_PRIVATE_MSG_ID]);
7651                } else if (msg.what >= FIRST_PACKAGE_MSG_ID
7652                        && msg.what <= LAST_PACKAGE_MSG_ID) {
7653                    Log.v(LOGTAG, HandlerPackageDebugString[msg.what
7654                            - FIRST_PACKAGE_MSG_ID]);
7655                } else {
7656                    Log.v(LOGTAG, Integer.toString(msg.what));
7657                }
7658            }
7659            if (mWebViewCore == null) {
7660                // after WebView's destroy() is called, skip handling messages.
7661                return;
7662            }
7663            switch (msg.what) {
7664                case REMEMBER_PASSWORD: {
7665                    mDatabase.setUsernamePassword(
7666                            msg.getData().getString("host"),
7667                            msg.getData().getString("username"),
7668                            msg.getData().getString("password"));
7669                    ((Message) msg.obj).sendToTarget();
7670                    break;
7671                }
7672                case NEVER_REMEMBER_PASSWORD: {
7673                    mDatabase.setUsernamePassword(
7674                            msg.getData().getString("host"), null, null);
7675                    ((Message) msg.obj).sendToTarget();
7676                    break;
7677                }
7678                case PREVENT_DEFAULT_TIMEOUT: {
7679                    // if timeout happens, cancel it so that it won't block UI
7680                    // to continue handling touch events
7681                    if ((msg.arg1 == MotionEvent.ACTION_DOWN
7682                            && mPreventDefault == PREVENT_DEFAULT_MAYBE_YES)
7683                            || (msg.arg1 == MotionEvent.ACTION_MOVE
7684                            && mPreventDefault == PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN)) {
7685                        cancelWebCoreTouchEvent(
7686                                viewToContentX(mLastTouchX + mScrollX),
7687                                viewToContentY(mLastTouchY + mScrollY),
7688                                true);
7689                    }
7690                    break;
7691                }
7692                case SCROLL_SELECT_TEXT: {
7693                    if (mAutoScrollX == 0 && mAutoScrollY == 0) {
7694                        mSentAutoScrollMessage = false;
7695                        break;
7696                    }
7697                    if (mScrollingLayer == 0) {
7698                        pinScrollBy(mAutoScrollX, mAutoScrollY, true, 0);
7699                    } else {
7700                        mScrollingLayerRect.left += mAutoScrollX;
7701                        mScrollingLayerRect.top += mAutoScrollY;
7702                        nativeScrollLayer(mScrollingLayer,
7703                                mScrollingLayerRect.left,
7704                                mScrollingLayerRect.top);
7705                        invalidate();
7706                    }
7707                    sendEmptyMessageDelayed(
7708                            SCROLL_SELECT_TEXT, SELECT_SCROLL_INTERVAL);
7709                    break;
7710                }
7711                case SWITCH_TO_SHORTPRESS: {
7712                    mInitialHitTestResult = null; // set by updateSelection()
7713                    if (mTouchMode == TOUCH_INIT_MODE) {
7714                        if (!getSettings().supportTouchOnly()
7715                                && mPreventDefault != PREVENT_DEFAULT_YES) {
7716                            mTouchMode = TOUCH_SHORTPRESS_START_MODE;
7717                            updateSelection();
7718                        } else {
7719                            // set to TOUCH_SHORTPRESS_MODE so that it won't
7720                            // trigger double tap any more
7721                            mTouchMode = TOUCH_SHORTPRESS_MODE;
7722                        }
7723                    } else if (mTouchMode == TOUCH_DOUBLE_TAP_MODE) {
7724                        mTouchMode = TOUCH_DONE_MODE;
7725                    }
7726                    break;
7727                }
7728                case SWITCH_TO_LONGPRESS: {
7729                    if (getSettings().supportTouchOnly()) {
7730                        removeTouchHighlight(false);
7731                    }
7732                    if (inFullScreenMode() || mDeferTouchProcess) {
7733                        TouchEventData ted = new TouchEventData();
7734                        ted.mAction = WebViewCore.ACTION_LONGPRESS;
7735                        ted.mIds = new int[1];
7736                        ted.mIds[0] = 0;
7737                        ted.mPoints = new Point[1];
7738                        ted.mPoints[0] = new Point(viewToContentX(mLastTouchX + mScrollX),
7739                                                   viewToContentY(mLastTouchY + mScrollY));
7740                        ted.mPointsInView = new Point[1];
7741                        ted.mPointsInView[0] = new Point(mLastTouchX, mLastTouchY);
7742                        // metaState for long press is tricky. Should it be the
7743                        // state when the press started or when the press was
7744                        // released? Or some intermediary key state? For
7745                        // simplicity for now, we don't set it.
7746                        ted.mMetaState = 0;
7747                        ted.mReprocess = mDeferTouchProcess;
7748                        ted.mNativeLayer = nativeScrollableLayer(
7749                                ted.mPoints[0].x, ted.mPoints[0].y,
7750                                ted.mNativeLayerRect, null);
7751                        ted.mSequence = mTouchEventQueue.nextTouchSequence();
7752                        mTouchEventQueue.preQueueTouchEventData(ted);
7753                        mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
7754                    } else if (mPreventDefault != PREVENT_DEFAULT_YES) {
7755                        mTouchMode = TOUCH_DONE_MODE;
7756                        performLongClick();
7757                    }
7758                    break;
7759                }
7760                case RELEASE_SINGLE_TAP: {
7761                    doShortPress();
7762                    break;
7763                }
7764                case SCROLL_TO_MSG_ID: {
7765                    // arg1 = animate, arg2 = onlyIfImeIsShowing
7766                    // obj = Point(x, y)
7767                    if (msg.arg2 == 1) {
7768                        // This scroll is intended to bring the textfield into
7769                        // view, but is only necessary if the IME is showing
7770                        InputMethodManager imm = InputMethodManager.peekInstance();
7771                        if (imm == null || !imm.isAcceptingText()
7772                                || (!imm.isActive(WebView.this) && (!inEditingMode()
7773                                || !imm.isActive(mWebTextView)))) {
7774                            break;
7775                        }
7776                    }
7777                    final Point p = (Point) msg.obj;
7778                    if (msg.arg1 == 1) {
7779                        spawnContentScrollTo(p.x, p.y);
7780                    } else {
7781                        setContentScrollTo(p.x, p.y);
7782                    }
7783                    break;
7784                }
7785                case UPDATE_ZOOM_RANGE: {
7786                    WebViewCore.ViewState viewState = (WebViewCore.ViewState) msg.obj;
7787                    // mScrollX contains the new minPrefWidth
7788                    mZoomManager.updateZoomRange(viewState, getViewWidth(), viewState.mScrollX);
7789                    break;
7790                }
7791                case REPLACE_BASE_CONTENT: {
7792                    nativeReplaceBaseContent(msg.arg1);
7793                    break;
7794                }
7795                case NEW_PICTURE_MSG_ID: {
7796                    // called for new content
7797                    final WebViewCore.DrawData draw = (WebViewCore.DrawData) msg.obj;
7798                    WebViewCore.ViewState viewState = draw.mViewState;
7799                    boolean isPictureAfterFirstLayout = viewState != null;
7800                    setBaseLayer(draw.mBaseLayer, draw.mInvalRegion,
7801                            getSettings().getShowVisualIndicator(),
7802                            isPictureAfterFirstLayout);
7803                    final Point viewSize = draw.mViewSize;
7804                    if (isPictureAfterFirstLayout) {
7805                        // Reset the last sent data here since dealing with new page.
7806                        mLastWidthSent = 0;
7807                        mZoomManager.onFirstLayout(draw);
7808                        if (!mDrawHistory) {
7809                            // Do not send the scroll event for this particular
7810                            // scroll message.  Note that a scroll event may
7811                            // still be fired if the user scrolls before the
7812                            // message can be handled.
7813                            mSendScrollEvent = false;
7814                            setContentScrollTo(viewState.mScrollX, viewState.mScrollY);
7815                            mSendScrollEvent = true;
7816
7817                            // As we are on a new page, remove the WebTextView. This
7818                            // is necessary for page loads driven by webkit, and in
7819                            // particular when the user was on a password field, so
7820                            // the WebTextView was visible.
7821                            clearTextEntry();
7822                        }
7823                    }
7824
7825                    // We update the layout (i.e. request a layout from the
7826                    // view system) if the last view size that we sent to
7827                    // WebCore matches the view size of the picture we just
7828                    // received in the fixed dimension.
7829                    final boolean updateLayout = viewSize.x == mLastWidthSent
7830                            && viewSize.y == mLastHeightSent;
7831                    // Don't send scroll event for picture coming from webkit,
7832                    // since the new picture may cause a scroll event to override
7833                    // the saved history scroll position.
7834                    mSendScrollEvent = false;
7835                    recordNewContentSize(draw.mContentSize.x,
7836                            draw.mContentSize.y, updateLayout);
7837                    mSendScrollEvent = true;
7838                    if (DebugFlags.WEB_VIEW) {
7839                        Rect b = draw.mInvalRegion.getBounds();
7840                        Log.v(LOGTAG, "NEW_PICTURE_MSG_ID {" +
7841                                b.left+","+b.top+","+b.right+","+b.bottom+"}");
7842                    }
7843                    invalidateContentRect(draw.mInvalRegion.getBounds());
7844
7845                    if (mPictureListener != null) {
7846                        mPictureListener.onNewPicture(WebView.this, capturePicture());
7847                    }
7848
7849                    // update the zoom information based on the new picture
7850                    mZoomManager.onNewPicture(draw);
7851
7852                    if (draw.mFocusSizeChanged && inEditingMode()) {
7853                        mFocusSizeChanged = true;
7854                    }
7855                    if (isPictureAfterFirstLayout) {
7856                        mViewManager.postReadyToDrawAll();
7857                    }
7858                    break;
7859                }
7860                case WEBCORE_INITIALIZED_MSG_ID:
7861                    // nativeCreate sets mNativeClass to a non-zero value
7862                    String drawableDir = BrowserFrame.getRawResFilename(
7863                            BrowserFrame.DRAWABLEDIR, mContext);
7864                    AssetManager am = mContext.getAssets();
7865                    nativeCreate(msg.arg1, drawableDir, am);
7866                    break;
7867                case UPDATE_TEXTFIELD_TEXT_MSG_ID:
7868                    // Make sure that the textfield is currently focused
7869                    // and representing the same node as the pointer.
7870                    if (inEditingMode() &&
7871                            mWebTextView.isSameTextField(msg.arg1)) {
7872                        if (msg.getData().getBoolean("password")) {
7873                            Spannable text = (Spannable) mWebTextView.getText();
7874                            int start = Selection.getSelectionStart(text);
7875                            int end = Selection.getSelectionEnd(text);
7876                            mWebTextView.setInPassword(true);
7877                            // Restore the selection, which may have been
7878                            // ruined by setInPassword.
7879                            Spannable pword =
7880                                    (Spannable) mWebTextView.getText();
7881                            Selection.setSelection(pword, start, end);
7882                        // If the text entry has created more events, ignore
7883                        // this one.
7884                        } else if (msg.arg2 == mTextGeneration) {
7885                            String text = (String) msg.obj;
7886                            if (null == text) {
7887                                text = "";
7888                            }
7889                            mWebTextView.setTextAndKeepSelection(text);
7890                        }
7891                    }
7892                    break;
7893                case REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID:
7894                    displaySoftKeyboard(true);
7895                    // fall through to UPDATE_TEXT_SELECTION_MSG_ID
7896                case UPDATE_TEXT_SELECTION_MSG_ID:
7897                    updateTextSelectionFromMessage(msg.arg1, msg.arg2,
7898                            (WebViewCore.TextSelectionData) msg.obj);
7899                    break;
7900                case FORM_DID_BLUR:
7901                    if (inEditingMode()
7902                            && mWebTextView.isSameTextField(msg.arg1)) {
7903                        hideSoftKeyboard();
7904                    }
7905                    break;
7906                case RETURN_LABEL:
7907                    if (inEditingMode()
7908                            && mWebTextView.isSameTextField(msg.arg1)) {
7909                        mWebTextView.setHint((String) msg.obj);
7910                        InputMethodManager imm
7911                                = InputMethodManager.peekInstance();
7912                        // The hint is propagated to the IME in
7913                        // onCreateInputConnection.  If the IME is already
7914                        // active, restart it so that its hint text is updated.
7915                        if (imm != null && imm.isActive(mWebTextView)) {
7916                            imm.restartInput(mWebTextView);
7917                        }
7918                    }
7919                    break;
7920                case UNHANDLED_NAV_KEY:
7921                    navHandledKey(msg.arg1, 1, false, 0);
7922                    break;
7923                case UPDATE_TEXT_ENTRY_MSG_ID:
7924                    // this is sent after finishing resize in WebViewCore. Make
7925                    // sure the text edit box is still on the  screen.
7926                    if (inEditingMode() && nativeCursorIsTextInput()) {
7927                        rebuildWebTextView();
7928                    }
7929                    break;
7930                case CLEAR_TEXT_ENTRY:
7931                    clearTextEntry();
7932                    break;
7933                case INVAL_RECT_MSG_ID: {
7934                    Rect r = (Rect)msg.obj;
7935                    if (r == null) {
7936                        invalidate();
7937                    } else {
7938                        // we need to scale r from content into view coords,
7939                        // which viewInvalidate() does for us
7940                        viewInvalidate(r.left, r.top, r.right, r.bottom);
7941                    }
7942                    break;
7943                }
7944                case REQUEST_FORM_DATA:
7945                    AutoCompleteAdapter adapter = (AutoCompleteAdapter) msg.obj;
7946                    if (mWebTextView.isSameTextField(msg.arg1)) {
7947                        mWebTextView.setAdapterCustom(adapter);
7948                    }
7949                    break;
7950                case RESUME_WEBCORE_PRIORITY:
7951                    WebViewCore.resumePriority();
7952                    WebViewCore.resumeUpdatePicture(mWebViewCore);
7953                    break;
7954
7955                case LONG_PRESS_CENTER:
7956                    // as this is shared by keydown and trackballdown, reset all
7957                    // the states
7958                    mGotCenterDown = false;
7959                    mTrackballDown = false;
7960                    performLongClick();
7961                    break;
7962
7963                case WEBCORE_NEED_TOUCH_EVENTS:
7964                    mForwardTouchEvents = (msg.arg1 != 0);
7965                    break;
7966
7967                case PREVENT_TOUCH_ID:
7968                    if (inFullScreenMode()) {
7969                        break;
7970                    }
7971                    TouchEventData ted = (TouchEventData) msg.obj;
7972
7973                    if (mTouchEventQueue.enqueueTouchEvent(ted)) {
7974                        // WebCore is responding to us; remove pending timeout.
7975                        // It will be re-posted when needed.
7976                        removeMessages(PREVENT_DEFAULT_TIMEOUT);
7977                    }
7978                    break;
7979
7980                case REQUEST_KEYBOARD:
7981                    if (msg.arg1 == 0) {
7982                        hideSoftKeyboard();
7983                    } else {
7984                        displaySoftKeyboard(false);
7985                    }
7986                    break;
7987
7988                case FIND_AGAIN:
7989                    // Ignore if find has been dismissed.
7990                    if (mFindIsUp && mFindCallback != null) {
7991                        mFindCallback.findAll();
7992                    }
7993                    break;
7994
7995                case DRAG_HELD_MOTIONLESS:
7996                    mHeldMotionless = MOTIONLESS_TRUE;
7997                    invalidate();
7998                    // fall through to keep scrollbars awake
7999
8000                case AWAKEN_SCROLL_BARS:
8001                    if (mTouchMode == TOUCH_DRAG_MODE
8002                            && mHeldMotionless == MOTIONLESS_TRUE) {
8003                        awakenScrollBars(ViewConfiguration
8004                                .getScrollDefaultDelay(), false);
8005                        mPrivateHandler.sendMessageDelayed(mPrivateHandler
8006                                .obtainMessage(AWAKEN_SCROLL_BARS),
8007                                ViewConfiguration.getScrollDefaultDelay());
8008                    }
8009                    break;
8010
8011                case DO_MOTION_UP:
8012                    doMotionUp(msg.arg1, msg.arg2);
8013                    break;
8014
8015                case SCREEN_ON:
8016                    setKeepScreenOn(msg.arg1 == 1);
8017                    break;
8018
8019                case ENTER_FULLSCREEN_VIDEO:
8020                    int layerId = msg.arg1;
8021
8022                    String url = (String) msg.obj;
8023                    if (mHTML5VideoViewProxy != null) {
8024                        mHTML5VideoViewProxy.enterFullScreenVideo(layerId, url);
8025                    }
8026                    break;
8027
8028                case SHOW_FULLSCREEN: {
8029                    View view = (View) msg.obj;
8030                    int npp = msg.arg1;
8031
8032                    if (inFullScreenMode()) {
8033                        Log.w(LOGTAG, "Should not have another full screen.");
8034                        dismissFullScreenMode();
8035                    }
8036                    mFullScreenHolder = new PluginFullScreenHolder(WebView.this, npp);
8037                    mFullScreenHolder.setContentView(view);
8038                    mFullScreenHolder.setCancelable(false);
8039                    mFullScreenHolder.setCanceledOnTouchOutside(false);
8040                    mFullScreenHolder.show();
8041
8042                    break;
8043                }
8044                case HIDE_FULLSCREEN:
8045                    dismissFullScreenMode();
8046                    break;
8047
8048                case DOM_FOCUS_CHANGED:
8049                    if (inEditingMode()) {
8050                        nativeClearCursor();
8051                        rebuildWebTextView();
8052                    }
8053                    break;
8054
8055                case SHOW_RECT_MSG_ID: {
8056                    WebViewCore.ShowRectData data = (WebViewCore.ShowRectData) msg.obj;
8057                    int x = mScrollX;
8058                    int left = contentToViewX(data.mLeft);
8059                    int width = contentToViewDimension(data.mWidth);
8060                    int maxWidth = contentToViewDimension(data.mContentWidth);
8061                    int viewWidth = getViewWidth();
8062                    if (width < viewWidth) {
8063                        // center align
8064                        x += left + width / 2 - mScrollX - viewWidth / 2;
8065                    } else {
8066                        x += (int) (left + data.mXPercentInDoc * width
8067                                - mScrollX - data.mXPercentInView * viewWidth);
8068                    }
8069                    if (DebugFlags.WEB_VIEW) {
8070                        Log.v(LOGTAG, "showRectMsg=(left=" + left + ",width=" +
8071                              width + ",maxWidth=" + maxWidth +
8072                              ",viewWidth=" + viewWidth + ",x="
8073                              + x + ",xPercentInDoc=" + data.mXPercentInDoc +
8074                              ",xPercentInView=" + data.mXPercentInView+ ")");
8075                    }
8076                    // use the passing content width to cap x as the current
8077                    // mContentWidth may not be updated yet
8078                    x = Math.max(0,
8079                            (Math.min(maxWidth, x + viewWidth)) - viewWidth);
8080                    int top = contentToViewY(data.mTop);
8081                    int height = contentToViewDimension(data.mHeight);
8082                    int maxHeight = contentToViewDimension(data.mContentHeight);
8083                    int viewHeight = getViewHeight();
8084                    int y = (int) (top + data.mYPercentInDoc * height -
8085                                   data.mYPercentInView * viewHeight);
8086                    if (DebugFlags.WEB_VIEW) {
8087                        Log.v(LOGTAG, "showRectMsg=(top=" + top + ",height=" +
8088                              height + ",maxHeight=" + maxHeight +
8089                              ",viewHeight=" + viewHeight + ",y="
8090                              + y + ",yPercentInDoc=" + data.mYPercentInDoc +
8091                              ",yPercentInView=" + data.mYPercentInView+ ")");
8092                    }
8093                    // use the passing content height to cap y as the current
8094                    // mContentHeight may not be updated yet
8095                    y = Math.max(0,
8096                            (Math.min(maxHeight, y + viewHeight) - viewHeight));
8097                    // We need to take into account the visible title height
8098                    // when scrolling since y is an absolute view position.
8099                    y = Math.max(0, y - getVisibleTitleHeight());
8100                    scrollTo(x, y);
8101                    }
8102                    break;
8103
8104                case CENTER_FIT_RECT:
8105                    centerFitRect((Rect)msg.obj);
8106                    break;
8107
8108                case SET_SCROLLBAR_MODES:
8109                    mHorizontalScrollBarMode = msg.arg1;
8110                    mVerticalScrollBarMode = msg.arg2;
8111                    break;
8112
8113                case SELECTION_STRING_CHANGED:
8114                    if (mAccessibilityInjector != null) {
8115                        String selectionString = (String) msg.obj;
8116                        mAccessibilityInjector.onSelectionStringChange(selectionString);
8117                    }
8118                    break;
8119
8120                case SET_TOUCH_HIGHLIGHT_RECTS:
8121                    invalidate(mTouchHighlightRegion.getBounds());
8122                    mTouchHighlightRegion.setEmpty();
8123                    if (msg.obj != null) {
8124                        ArrayList<Rect> rects = (ArrayList<Rect>) msg.obj;
8125                        for (Rect rect : rects) {
8126                            Rect viewRect = contentToViewRect(rect);
8127                            // some sites, like stories in nytimes.com, set
8128                            // mouse event handler in the top div. It is not
8129                            // user friendly to highlight the div if it covers
8130                            // more than half of the screen.
8131                            if (viewRect.width() < getWidth() >> 1
8132                                    || viewRect.height() < getHeight() >> 1) {
8133                                mTouchHighlightRegion.union(viewRect);
8134                                invalidate(viewRect);
8135                            } else {
8136                                Log.w(LOGTAG, "Skip the huge selection rect:"
8137                                        + viewRect);
8138                            }
8139                        }
8140                    }
8141                    break;
8142
8143                case SAVE_WEBARCHIVE_FINISHED:
8144                    SaveWebArchiveMessage saveMessage = (SaveWebArchiveMessage)msg.obj;
8145                    if (saveMessage.mCallback != null) {
8146                        saveMessage.mCallback.onReceiveValue(saveMessage.mResultFile);
8147                    }
8148                    break;
8149
8150                case SET_AUTOFILLABLE:
8151                    mAutoFillData = (WebViewCore.AutoFillData) msg.obj;
8152                    if (mWebTextView != null) {
8153                        mWebTextView.setAutoFillable(mAutoFillData.getQueryId());
8154                        rebuildWebTextView();
8155                    }
8156                    break;
8157
8158                case AUTOFILL_COMPLETE:
8159                    if (mWebTextView != null) {
8160                        // Clear the WebTextView adapter when AutoFill finishes
8161                        // so that the drop down gets cleared.
8162                        mWebTextView.setAdapterCustom(null);
8163                    }
8164                    break;
8165
8166                case SELECT_AT:
8167                    nativeSelectAt(msg.arg1, msg.arg2);
8168                    break;
8169
8170                default:
8171                    super.handleMessage(msg);
8172                    break;
8173            }
8174        }
8175    }
8176
8177    /**
8178     * Used when receiving messages for REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID
8179     * and UPDATE_TEXT_SELECTION_MSG_ID.  Update the selection of WebTextView.
8180     */
8181    private void updateTextSelectionFromMessage(int nodePointer,
8182            int textGeneration, WebViewCore.TextSelectionData data) {
8183        if (inEditingMode()
8184                && mWebTextView.isSameTextField(nodePointer)
8185                && textGeneration == mTextGeneration) {
8186            mWebTextView.setSelectionFromWebKit(data.mStart, data.mEnd);
8187        }
8188    }
8189
8190    // Class used to use a dropdown for a <select> element
8191    private class InvokeListBox implements Runnable {
8192        // Whether the listbox allows multiple selection.
8193        private boolean     mMultiple;
8194        // Passed in to a list with multiple selection to tell
8195        // which items are selected.
8196        private int[]       mSelectedArray;
8197        // Passed in to a list with single selection to tell
8198        // where the initial selection is.
8199        private int         mSelection;
8200
8201        private Container[] mContainers;
8202
8203        // Need these to provide stable ids to my ArrayAdapter,
8204        // which normally does not have stable ids. (Bug 1250098)
8205        private class Container extends Object {
8206            /**
8207             * Possible values for mEnabled.  Keep in sync with OptionStatus in
8208             * WebViewCore.cpp
8209             */
8210            final static int OPTGROUP = -1;
8211            final static int OPTION_DISABLED = 0;
8212            final static int OPTION_ENABLED = 1;
8213
8214            String  mString;
8215            int     mEnabled;
8216            int     mId;
8217
8218            @Override
8219            public String toString() {
8220                return mString;
8221            }
8222        }
8223
8224        /**
8225         *  Subclass ArrayAdapter so we can disable OptionGroupLabels,
8226         *  and allow filtering.
8227         */
8228        private class MyArrayListAdapter extends ArrayAdapter<Container> {
8229            public MyArrayListAdapter() {
8230                super(mContext,
8231                        mMultiple ? com.android.internal.R.layout.select_dialog_multichoice :
8232                        com.android.internal.R.layout.webview_select_singlechoice,
8233                        mContainers);
8234            }
8235
8236            @Override
8237            public View getView(int position, View convertView,
8238                    ViewGroup parent) {
8239                // Always pass in null so that we will get a new CheckedTextView
8240                // Otherwise, an item which was previously used as an <optgroup>
8241                // element (i.e. has no check), could get used as an <option>
8242                // element, which needs a checkbox/radio, but it would not have
8243                // one.
8244                convertView = super.getView(position, null, parent);
8245                Container c = item(position);
8246                if (c != null && Container.OPTION_ENABLED != c.mEnabled) {
8247                    // ListView does not draw dividers between disabled and
8248                    // enabled elements.  Use a LinearLayout to provide dividers
8249                    LinearLayout layout = new LinearLayout(mContext);
8250                    layout.setOrientation(LinearLayout.VERTICAL);
8251                    if (position > 0) {
8252                        View dividerTop = new View(mContext);
8253                        dividerTop.setBackgroundResource(
8254                                android.R.drawable.divider_horizontal_bright);
8255                        layout.addView(dividerTop);
8256                    }
8257
8258                    if (Container.OPTGROUP == c.mEnabled) {
8259                        // Currently select_dialog_multichoice uses CheckedTextViews.
8260                        // If that changes, the class cast will no longer be valid.
8261                        if (mMultiple) {
8262                            Assert.assertTrue(convertView instanceof CheckedTextView);
8263                            ((CheckedTextView) convertView).setCheckMarkDrawable(null);
8264                        }
8265                    } else {
8266                        // c.mEnabled == Container.OPTION_DISABLED
8267                        // Draw the disabled element in a disabled state.
8268                        convertView.setEnabled(false);
8269                    }
8270
8271                    layout.addView(convertView);
8272                    if (position < getCount() - 1) {
8273                        View dividerBottom = new View(mContext);
8274                        dividerBottom.setBackgroundResource(
8275                                android.R.drawable.divider_horizontal_bright);
8276                        layout.addView(dividerBottom);
8277                    }
8278                    return layout;
8279                }
8280                return convertView;
8281            }
8282
8283            @Override
8284            public boolean hasStableIds() {
8285                // AdapterView's onChanged method uses this to determine whether
8286                // to restore the old state.  Return false so that the old (out
8287                // of date) state does not replace the new, valid state.
8288                return false;
8289            }
8290
8291            private Container item(int position) {
8292                if (position < 0 || position >= getCount()) {
8293                    return null;
8294                }
8295                return (Container) getItem(position);
8296            }
8297
8298            @Override
8299            public long getItemId(int position) {
8300                Container item = item(position);
8301                if (item == null) {
8302                    return -1;
8303                }
8304                return item.mId;
8305            }
8306
8307            @Override
8308            public boolean areAllItemsEnabled() {
8309                return false;
8310            }
8311
8312            @Override
8313            public boolean isEnabled(int position) {
8314                Container item = item(position);
8315                if (item == null) {
8316                    return false;
8317                }
8318                return Container.OPTION_ENABLED == item.mEnabled;
8319            }
8320        }
8321
8322        private InvokeListBox(String[] array, int[] enabled, int[] selected) {
8323            mMultiple = true;
8324            mSelectedArray = selected;
8325
8326            int length = array.length;
8327            mContainers = new Container[length];
8328            for (int i = 0; i < length; i++) {
8329                mContainers[i] = new Container();
8330                mContainers[i].mString = array[i];
8331                mContainers[i].mEnabled = enabled[i];
8332                mContainers[i].mId = i;
8333            }
8334        }
8335
8336        private InvokeListBox(String[] array, int[] enabled, int selection) {
8337            mSelection = selection;
8338            mMultiple = false;
8339
8340            int length = array.length;
8341            mContainers = new Container[length];
8342            for (int i = 0; i < length; i++) {
8343                mContainers[i] = new Container();
8344                mContainers[i].mString = array[i];
8345                mContainers[i].mEnabled = enabled[i];
8346                mContainers[i].mId = i;
8347            }
8348        }
8349
8350        /*
8351         * Whenever the data set changes due to filtering, this class ensures
8352         * that the checked item remains checked.
8353         */
8354        private class SingleDataSetObserver extends DataSetObserver {
8355            private long        mCheckedId;
8356            private ListView    mListView;
8357            private Adapter     mAdapter;
8358
8359            /*
8360             * Create a new observer.
8361             * @param id The ID of the item to keep checked.
8362             * @param l ListView for getting and clearing the checked states
8363             * @param a Adapter for getting the IDs
8364             */
8365            public SingleDataSetObserver(long id, ListView l, Adapter a) {
8366                mCheckedId = id;
8367                mListView = l;
8368                mAdapter = a;
8369            }
8370
8371            @Override
8372            public void onChanged() {
8373                // The filter may have changed which item is checked.  Find the
8374                // item that the ListView thinks is checked.
8375                int position = mListView.getCheckedItemPosition();
8376                long id = mAdapter.getItemId(position);
8377                if (mCheckedId != id) {
8378                    // Clear the ListView's idea of the checked item, since
8379                    // it is incorrect
8380                    mListView.clearChoices();
8381                    // Search for mCheckedId.  If it is in the filtered list,
8382                    // mark it as checked
8383                    int count = mAdapter.getCount();
8384                    for (int i = 0; i < count; i++) {
8385                        if (mAdapter.getItemId(i) == mCheckedId) {
8386                            mListView.setItemChecked(i, true);
8387                            break;
8388                        }
8389                    }
8390                }
8391            }
8392        }
8393
8394        public void run() {
8395            final ListView listView = (ListView) LayoutInflater.from(mContext)
8396                    .inflate(com.android.internal.R.layout.select_dialog, null);
8397            final MyArrayListAdapter adapter = new MyArrayListAdapter();
8398            AlertDialog.Builder b = new AlertDialog.Builder(mContext)
8399                    .setView(listView).setCancelable(true)
8400                    .setInverseBackgroundForced(true);
8401
8402            if (mMultiple) {
8403                b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
8404                    public void onClick(DialogInterface dialog, int which) {
8405                        mWebViewCore.sendMessage(
8406                                EventHub.LISTBOX_CHOICES,
8407                                adapter.getCount(), 0,
8408                                listView.getCheckedItemPositions());
8409                    }});
8410                b.setNegativeButton(android.R.string.cancel,
8411                        new DialogInterface.OnClickListener() {
8412                    public void onClick(DialogInterface dialog, int which) {
8413                        mWebViewCore.sendMessage(
8414                                EventHub.SINGLE_LISTBOX_CHOICE, -2, 0);
8415                }});
8416            }
8417            mListBoxDialog = b.create();
8418            listView.setAdapter(adapter);
8419            listView.setFocusableInTouchMode(true);
8420            // There is a bug (1250103) where the checks in a ListView with
8421            // multiple items selected are associated with the positions, not
8422            // the ids, so the items do not properly retain their checks when
8423            // filtered.  Do not allow filtering on multiple lists until
8424            // that bug is fixed.
8425
8426            listView.setTextFilterEnabled(!mMultiple);
8427            if (mMultiple) {
8428                listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
8429                int length = mSelectedArray.length;
8430                for (int i = 0; i < length; i++) {
8431                    listView.setItemChecked(mSelectedArray[i], true);
8432                }
8433            } else {
8434                listView.setOnItemClickListener(new OnItemClickListener() {
8435                    public void onItemClick(AdapterView<?> parent, View v,
8436                            int position, long id) {
8437                        // Rather than sending the message right away, send it
8438                        // after the page regains focus.
8439                        mListBoxMessage = Message.obtain(null,
8440                                EventHub.SINGLE_LISTBOX_CHOICE, (int) id, 0);
8441                        mListBoxDialog.dismiss();
8442                        mListBoxDialog = null;
8443                    }
8444                });
8445                if (mSelection != -1) {
8446                    listView.setSelection(mSelection);
8447                    listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
8448                    listView.setItemChecked(mSelection, true);
8449                    DataSetObserver observer = new SingleDataSetObserver(
8450                            adapter.getItemId(mSelection), listView, adapter);
8451                    adapter.registerDataSetObserver(observer);
8452                }
8453            }
8454            mListBoxDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
8455                public void onCancel(DialogInterface dialog) {
8456                    mWebViewCore.sendMessage(
8457                                EventHub.SINGLE_LISTBOX_CHOICE, -2, 0);
8458                    mListBoxDialog = null;
8459                }
8460            });
8461            mListBoxDialog.show();
8462        }
8463    }
8464
8465    private Message mListBoxMessage;
8466
8467    /*
8468     * Request a dropdown menu for a listbox with multiple selection.
8469     *
8470     * @param array Labels for the listbox.
8471     * @param enabledArray  State for each element in the list.  See static
8472     *      integers in Container class.
8473     * @param selectedArray Which positions are initally selected.
8474     */
8475    void requestListBox(String[] array, int[] enabledArray, int[]
8476            selectedArray) {
8477        mPrivateHandler.post(
8478                new InvokeListBox(array, enabledArray, selectedArray));
8479    }
8480
8481    /*
8482     * Request a dropdown menu for a listbox with single selection or a single
8483     * <select> element.
8484     *
8485     * @param array Labels for the listbox.
8486     * @param enabledArray  State for each element in the list.  See static
8487     *      integers in Container class.
8488     * @param selection Which position is initally selected.
8489     */
8490    void requestListBox(String[] array, int[] enabledArray, int selection) {
8491        mPrivateHandler.post(
8492                new InvokeListBox(array, enabledArray, selection));
8493    }
8494
8495    // called by JNI
8496    private void sendMoveFocus(int frame, int node) {
8497        mWebViewCore.sendMessage(EventHub.SET_MOVE_FOCUS,
8498                new WebViewCore.CursorData(frame, node, 0, 0));
8499    }
8500
8501    // called by JNI
8502    private void sendMoveMouse(int frame, int node, int x, int y) {
8503        mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE,
8504                new WebViewCore.CursorData(frame, node, x, y));
8505    }
8506
8507    /*
8508     * Send a mouse move event to the webcore thread.
8509     *
8510     * @param removeFocus Pass true to remove the WebTextView, if present.
8511     * @param stopPaintingCaret Stop drawing the blinking caret if true.
8512     * called by JNI
8513     */
8514    @SuppressWarnings("unused")
8515    private void sendMoveMouseIfLatest(boolean removeFocus, boolean stopPaintingCaret) {
8516        if (removeFocus) {
8517            clearTextEntry();
8518        }
8519        mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE_IF_LATEST,
8520                stopPaintingCaret ? 1 : 0, 0,
8521                cursorData());
8522    }
8523
8524    /**
8525     * Called by JNI to send a message to the webcore thread that the user
8526     * touched the webpage.
8527     * @param touchGeneration Generation number of the touch, to ignore touches
8528     *      after a new one has been generated.
8529     * @param frame Pointer to the frame holding the node that was touched.
8530     * @param node Pointer to the node touched.
8531     * @param x x-position of the touch.
8532     * @param y y-position of the touch.
8533     */
8534    private void sendMotionUp(int touchGeneration,
8535            int frame, int node, int x, int y) {
8536        WebViewCore.TouchUpData touchUpData = new WebViewCore.TouchUpData();
8537        touchUpData.mMoveGeneration = touchGeneration;
8538        touchUpData.mFrame = frame;
8539        touchUpData.mNode = node;
8540        touchUpData.mX = x;
8541        touchUpData.mY = y;
8542        touchUpData.mNativeLayer = nativeScrollableLayer(
8543                x, y, touchUpData.mNativeLayerRect, null);
8544        mWebViewCore.sendMessage(EventHub.TOUCH_UP, touchUpData);
8545    }
8546
8547
8548    private int getScaledMaxXScroll() {
8549        int width;
8550        if (mHeightCanMeasure == false) {
8551            width = getViewWidth() / 4;
8552        } else {
8553            Rect visRect = new Rect();
8554            calcOurVisibleRect(visRect);
8555            width = visRect.width() / 2;
8556        }
8557        // FIXME the divisor should be retrieved from somewhere
8558        return viewToContentX(width);
8559    }
8560
8561    private int getScaledMaxYScroll() {
8562        int height;
8563        if (mHeightCanMeasure == false) {
8564            height = getViewHeight() / 4;
8565        } else {
8566            Rect visRect = new Rect();
8567            calcOurVisibleRect(visRect);
8568            height = visRect.height() / 2;
8569        }
8570        // FIXME the divisor should be retrieved from somewhere
8571        // the closest thing today is hard-coded into ScrollView.java
8572        // (from ScrollView.java, line 363)   int maxJump = height/2;
8573        return Math.round(height * mZoomManager.getInvScale());
8574    }
8575
8576    /**
8577     * Called by JNI to invalidate view
8578     */
8579    private void viewInvalidate() {
8580        invalidate();
8581    }
8582
8583    /**
8584     * Pass the key directly to the page.  This assumes that
8585     * nativePageShouldHandleShiftAndArrows() returned true.
8586     */
8587    private void letPageHandleNavKey(int keyCode, long time, boolean down, int metaState) {
8588        int keyEventAction;
8589        int eventHubAction;
8590        if (down) {
8591            keyEventAction = KeyEvent.ACTION_DOWN;
8592            eventHubAction = EventHub.KEY_DOWN;
8593            playSoundEffect(keyCodeToSoundsEffect(keyCode));
8594        } else {
8595            keyEventAction = KeyEvent.ACTION_UP;
8596            eventHubAction = EventHub.KEY_UP;
8597        }
8598
8599        KeyEvent event = new KeyEvent(time, time, keyEventAction, keyCode,
8600                1, (metaState & KeyEvent.META_SHIFT_ON)
8601                | (metaState & KeyEvent.META_ALT_ON)
8602                | (metaState & KeyEvent.META_SYM_ON)
8603                , KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0);
8604        mWebViewCore.sendMessage(eventHubAction, event);
8605    }
8606
8607    // return true if the key was handled
8608    private boolean navHandledKey(int keyCode, int count, boolean noScroll,
8609            long time) {
8610        if (mNativeClass == 0) {
8611            return false;
8612        }
8613        mInitialHitTestResult = null;
8614        mLastCursorTime = time;
8615        mLastCursorBounds = nativeGetCursorRingBounds();
8616        boolean keyHandled
8617                = nativeMoveCursor(keyCode, count, noScroll) == false;
8618        if (DebugFlags.WEB_VIEW) {
8619            Log.v(LOGTAG, "navHandledKey mLastCursorBounds=" + mLastCursorBounds
8620                    + " mLastCursorTime=" + mLastCursorTime
8621                    + " handled=" + keyHandled);
8622        }
8623        if (keyHandled == false) {
8624            return keyHandled;
8625        }
8626        Rect contentCursorRingBounds = nativeGetCursorRingBounds();
8627        if (contentCursorRingBounds.isEmpty()) return keyHandled;
8628        Rect viewCursorRingBounds = contentToViewRect(contentCursorRingBounds);
8629        // set last touch so that context menu related functions will work
8630        mLastTouchX = (viewCursorRingBounds.left + viewCursorRingBounds.right) / 2;
8631        mLastTouchY = (viewCursorRingBounds.top + viewCursorRingBounds.bottom) / 2;
8632        if (mHeightCanMeasure == false) {
8633            return keyHandled;
8634        }
8635        Rect visRect = new Rect();
8636        calcOurVisibleRect(visRect);
8637        Rect outset = new Rect(visRect);
8638        int maxXScroll = visRect.width() / 2;
8639        int maxYScroll = visRect.height() / 2;
8640        outset.inset(-maxXScroll, -maxYScroll);
8641        if (Rect.intersects(outset, viewCursorRingBounds) == false) {
8642            return keyHandled;
8643        }
8644        // FIXME: Necessary because ScrollView/ListView do not scroll left/right
8645        int maxH = Math.min(viewCursorRingBounds.right - visRect.right,
8646                maxXScroll);
8647        if (maxH > 0) {
8648            pinScrollBy(maxH, 0, true, 0);
8649        } else {
8650            maxH = Math.max(viewCursorRingBounds.left - visRect.left,
8651                    -maxXScroll);
8652            if (maxH < 0) {
8653                pinScrollBy(maxH, 0, true, 0);
8654            }
8655        }
8656        if (mLastCursorBounds.isEmpty()) return keyHandled;
8657        if (mLastCursorBounds.equals(contentCursorRingBounds)) {
8658            return keyHandled;
8659        }
8660        if (DebugFlags.WEB_VIEW) {
8661            Log.v(LOGTAG, "navHandledKey contentCursorRingBounds="
8662                    + contentCursorRingBounds);
8663        }
8664        requestRectangleOnScreen(viewCursorRingBounds);
8665        return keyHandled;
8666    }
8667
8668    /**
8669     * @return Whether accessibility script has been injected.
8670     */
8671    private boolean accessibilityScriptInjected() {
8672        // TODO: Maybe the injected script should announce its presence in
8673        // the page meta-tag so the nativePageShouldHandleShiftAndArrows
8674        // will check that as one of the conditions it looks for
8675        return mAccessibilityScriptInjected;
8676    }
8677
8678    /**
8679     * Set the background color. It's white by default. Pass
8680     * zero to make the view transparent.
8681     * @param color   the ARGB color described by Color.java
8682     */
8683    @Override
8684    public void setBackgroundColor(int color) {
8685        mBackgroundColor = color;
8686        mWebViewCore.sendMessage(EventHub.SET_BACKGROUND_COLOR, color);
8687    }
8688
8689    public void debugDump() {
8690        nativeDebugDump();
8691        mWebViewCore.sendMessage(EventHub.DUMP_NAVTREE);
8692    }
8693
8694    /**
8695     * Draw the HTML page into the specified canvas. This call ignores any
8696     * view-specific zoom, scroll offset, or other changes. It does not draw
8697     * any view-specific chrome, such as progress or URL bars.
8698     *
8699     * @hide only needs to be accessible to Browser and testing
8700     */
8701    public void drawPage(Canvas canvas) {
8702        nativeDraw(canvas, 0, 0, false);
8703    }
8704
8705    /**
8706     * Enable the communication b/t the webView and VideoViewProxy
8707     *
8708     * @hide only used by the Browser
8709     */
8710    public void setHTML5VideoViewProxy(HTML5VideoViewProxy proxy) {
8711        mHTML5VideoViewProxy = proxy;
8712    }
8713
8714    /**
8715     * Enable expanded tiles bound for smoother scrolling.
8716     *
8717     * @hide only used by the Browser
8718     */
8719    public void setExpandedTileBounds(boolean enabled) {
8720        nativeSetExpandedTileBounds(enabled);
8721    }
8722
8723    /**
8724     * Set the time to wait between passing touches to WebCore. See also the
8725     * TOUCH_SENT_INTERVAL member for further discussion.
8726     *
8727     * @hide This is only used by the DRT test application.
8728     */
8729    public void setTouchInterval(int interval) {
8730        mCurrentTouchInterval = interval;
8731    }
8732
8733    /**
8734     *  Update our cache with updatedText.
8735     *  @param updatedText  The new text to put in our cache.
8736     */
8737    /* package */ void updateCachedTextfield(String updatedText) {
8738        // Also place our generation number so that when we look at the cache
8739        // we recognize that it is up to date.
8740        nativeUpdateCachedTextfield(updatedText, mTextGeneration);
8741    }
8742
8743    /*package*/ void autoFillForm(int autoFillQueryId) {
8744        mWebViewCore.sendMessage(EventHub.AUTOFILL_FORM, autoFillQueryId, /* unused */0);
8745    }
8746
8747    /* package */ ViewManager getViewManager() {
8748        return mViewManager;
8749    }
8750
8751    private native int nativeCacheHitFramePointer();
8752    private native boolean  nativeCacheHitIsPlugin();
8753    private native Rect nativeCacheHitNodeBounds();
8754    private native int nativeCacheHitNodePointer();
8755    /* package */ native void nativeClearCursor();
8756    private native void     nativeCreate(int ptr, String drawableDir, AssetManager am);
8757    private native int      nativeCursorFramePointer();
8758    private native Rect     nativeCursorNodeBounds();
8759    private native int nativeCursorNodePointer();
8760    private native boolean  nativeCursorIntersects(Rect visibleRect);
8761    private native boolean  nativeCursorIsAnchor();
8762    private native boolean  nativeCursorIsTextInput();
8763    private native Point    nativeCursorPosition();
8764    private native String   nativeCursorText();
8765    /**
8766     * Returns true if the native cursor node says it wants to handle key events
8767     * (ala plugins). This can only be called if mNativeClass is non-zero!
8768     */
8769    private native boolean  nativeCursorWantsKeyEvents();
8770    private native void     nativeDebugDump();
8771    private native void     nativeDestroy();
8772
8773    /**
8774     * Draw the picture set with a background color and extra. If
8775     * "splitIfNeeded" is true and the return value is not 0, the return value
8776     * MUST be passed to WebViewCore with SPLIT_PICTURE_SET message so that the
8777     * native allocation can be freed.
8778     */
8779    private native int nativeDraw(Canvas canvas, int color, int extra,
8780            boolean splitIfNeeded);
8781    private native void     nativeDumpDisplayTree(String urlOrNull);
8782    private native boolean  nativeEvaluateLayersAnimations();
8783    private native int      nativeGetDrawGLFunction(Rect rect, Rect viewRect,
8784            float scale, int extras);
8785    private native void     nativeUpdateDrawGLFunction(Rect rect, Rect viewRect);
8786    private native void     nativeExtendSelection(int x, int y);
8787    private native int      nativeFindAll(String findLower, String findUpper,
8788            boolean sameAsLastSearch);
8789    private native void     nativeFindNext(boolean forward);
8790    /* package */ native int      nativeFocusCandidateFramePointer();
8791    /* package */ native boolean  nativeFocusCandidateHasNextTextfield();
8792    /* package */ native boolean  nativeFocusCandidateIsPassword();
8793    private native boolean  nativeFocusCandidateIsRtlText();
8794    private native boolean  nativeFocusCandidateIsTextInput();
8795    /* package */ native int      nativeFocusCandidateMaxLength();
8796    /* package */ native boolean  nativeFocusCandidateIsAutoComplete();
8797    /* package */ native String   nativeFocusCandidateName();
8798    private native Rect     nativeFocusCandidateNodeBounds();
8799    /**
8800     * @return A Rect with left, top, right, bottom set to the corresponding
8801     * padding values in the focus candidate, if it is a textfield/textarea with
8802     * a style.  Otherwise return null.  This is not actually a rectangle; Rect
8803     * is being used to pass four integers.
8804     */
8805    private native Rect     nativeFocusCandidatePaddingRect();
8806    /* package */ native int      nativeFocusCandidatePointer();
8807    private native String   nativeFocusCandidateText();
8808    /* package */ native float    nativeFocusCandidateTextSize();
8809    /* package */ native int nativeFocusCandidateLineHeight();
8810    /**
8811     * Returns an integer corresponding to WebView.cpp::type.
8812     * See WebTextView.setType()
8813     */
8814    private native int      nativeFocusCandidateType();
8815    private native boolean  nativeFocusIsPlugin();
8816    private native Rect     nativeFocusNodeBounds();
8817    /* package */ native int nativeFocusNodePointer();
8818    private native Rect     nativeGetCursorRingBounds();
8819    private native String   nativeGetSelection();
8820    private native boolean  nativeHasCursorNode();
8821    private native boolean  nativeHasFocusNode();
8822    private native void     nativeHideCursor();
8823    private native boolean  nativeHitSelection(int x, int y);
8824    private native String   nativeImageURI(int x, int y);
8825    private native void     nativeInstrumentReport();
8826    private native Rect     nativeLayerBounds(int layer);
8827    /* package */ native boolean nativeMoveCursorToNextTextInput();
8828    // return true if the page has been scrolled
8829    private native boolean  nativeMotionUp(int x, int y, int slop);
8830    // returns false if it handled the key
8831    private native boolean  nativeMoveCursor(int keyCode, int count,
8832            boolean noScroll);
8833    private native int      nativeMoveGeneration();
8834    private native void     nativeMoveSelection(int x, int y);
8835    /**
8836     * @return true if the page should get the shift and arrow keys, rather
8837     * than select text/navigation.
8838     *
8839     * If the focus is a plugin, or if the focus and cursor match and are
8840     * a contentEditable element, then the page should handle these keys.
8841     */
8842    private native boolean  nativePageShouldHandleShiftAndArrows();
8843    private native boolean  nativePointInNavCache(int x, int y, int slop);
8844    // Like many other of our native methods, you must make sure that
8845    // mNativeClass is not null before calling this method.
8846    private native void     nativeRecordButtons(boolean focused,
8847            boolean pressed, boolean invalidate);
8848    private native void     nativeResetSelection();
8849    private native Point    nativeSelectableText();
8850    private native void     nativeSelectAll();
8851    private native void     nativeSelectBestAt(Rect rect);
8852    private native void     nativeSelectAt(int x, int y);
8853    private native int      nativeSelectionX();
8854    private native int      nativeSelectionY();
8855    private native int      nativeFindIndex();
8856    private native void     nativeSetExtendSelection();
8857    private native void     nativeSetFindIsEmpty();
8858    private native void     nativeSetFindIsUp(boolean isUp);
8859    private native void     nativeSetHeightCanMeasure(boolean measure);
8860    private native void     nativeSetBaseLayer(int layer, Region invalRegion,
8861            boolean showVisualIndicator, boolean isPictureAfterFirstLayout);
8862    private native void     nativeShowCursorTimed();
8863    private native void     nativeReplaceBaseContent(int content);
8864    private native void     nativeCopyBaseContentToPicture(Picture pict);
8865    private native boolean  nativeHasContent();
8866    private native void     nativeSetSelectionPointer(boolean set,
8867            float scale, int x, int y);
8868    private native boolean  nativeStartSelection(int x, int y);
8869    private native void     nativeStopGL();
8870    private native Rect     nativeSubtractLayers(Rect content);
8871    private native int      nativeTextGeneration();
8872    // Never call this version except by updateCachedTextfield(String) -
8873    // we always want to pass in our generation number.
8874    private native void     nativeUpdateCachedTextfield(String updatedText,
8875            int generation);
8876    private native boolean  nativeWordSelection(int x, int y);
8877    // return NO_LEFTEDGE means failure.
8878    static final int NO_LEFTEDGE = -1;
8879    native int nativeGetBlockLeftEdge(int x, int y, float scale);
8880
8881    private native void nativeSetExpandedTileBounds(boolean enabled);
8882
8883    // Returns a pointer to the scrollable LayerAndroid at the given point.
8884    private native int      nativeScrollableLayer(int x, int y, Rect scrollRect,
8885            Rect scrollBounds);
8886    /**
8887     * Scroll the specified layer.
8888     * @param layer Id of the layer to scroll, as determined by nativeScrollableLayer.
8889     * @param newX Destination x position to which to scroll.
8890     * @param newY Destination y position to which to scroll.
8891     * @return True if the layer is successfully scrolled.
8892     */
8893    private native boolean  nativeScrollLayer(int layer, int newX, int newY);
8894}
8895