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