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