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