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