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