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