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