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