OSDN Git Service

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