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