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