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