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