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