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