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