WebView.java revision 79d99bebc19cf20cb486953ef2e9f6d96b27ab18
1/* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.webkit; 18 19import android.animation.ObjectAnimator; 20import android.annotation.Widget; 21import android.app.ActivityManager; 22import android.app.AlertDialog; 23import android.content.BroadcastReceiver; 24import android.content.ClipData; 25import android.content.ClipboardManager; 26import android.content.ComponentCallbacks2; 27import android.content.Context; 28import android.content.DialogInterface; 29import android.content.DialogInterface.OnCancelListener; 30import android.content.Intent; 31import android.content.IntentFilter; 32import android.content.pm.PackageManager; 33import android.content.res.Configuration; 34import android.database.DataSetObserver; 35import android.graphics.Bitmap; 36import android.graphics.BitmapFactory; 37import android.graphics.BitmapShader; 38import android.graphics.Canvas; 39import android.graphics.Color; 40import android.graphics.ColorFilter; 41import android.graphics.DrawFilter; 42import android.graphics.Paint; 43import android.graphics.PaintFlagsDrawFilter; 44import android.graphics.Picture; 45import android.graphics.Point; 46import android.graphics.Rect; 47import android.graphics.RectF; 48import android.graphics.Region; 49import android.graphics.RegionIterator; 50import android.graphics.Shader; 51import android.graphics.drawable.Drawable; 52import android.net.Proxy; 53import android.net.ProxyProperties; 54import android.net.Uri; 55import android.net.http.SslCertificate; 56import android.os.AsyncTask; 57import android.os.Bundle; 58import android.os.Handler; 59import android.os.Looper; 60import android.os.Message; 61import android.os.StrictMode; 62import android.os.SystemClock; 63import android.provider.Settings; 64import android.security.KeyChain; 65import android.speech.tts.TextToSpeech; 66import android.text.Editable; 67import android.text.InputType; 68import android.text.Selection; 69import android.text.TextUtils; 70import android.util.AttributeSet; 71import android.util.DisplayMetrics; 72import android.util.EventLog; 73import android.util.Log; 74import android.view.Display; 75import android.view.Gravity; 76import android.view.HapticFeedbackConstants; 77import android.view.HardwareCanvas; 78import android.view.InputDevice; 79import android.view.KeyCharacterMap; 80import android.view.KeyEvent; 81import android.view.LayoutInflater; 82import android.view.MotionEvent; 83import android.view.ScaleGestureDetector; 84import android.view.SoundEffectConstants; 85import android.view.VelocityTracker; 86import android.view.View; 87import android.view.ViewConfiguration; 88import android.view.ViewGroup; 89import android.view.ViewParent; 90import android.view.ViewTreeObserver; 91import android.view.WindowManager; 92import android.view.accessibility.AccessibilityEvent; 93import android.view.accessibility.AccessibilityManager; 94import android.view.accessibility.AccessibilityNodeInfo; 95import android.view.inputmethod.BaseInputConnection; 96import android.view.inputmethod.EditorInfo; 97import android.view.inputmethod.InputConnection; 98import android.view.inputmethod.InputMethodManager; 99import android.webkit.WebTextView.AutoCompleteAdapter; 100import android.webkit.WebViewCore.DrawData; 101import android.webkit.WebViewCore.EventHub; 102import android.webkit.WebViewCore.TextFieldInitData; 103import android.webkit.WebViewCore.TouchEventData; 104import android.webkit.WebViewCore.TouchHighlightData; 105import android.webkit.WebViewCore.WebKitHitTest; 106import android.widget.AbsoluteLayout; 107import android.widget.Adapter; 108import android.widget.AdapterView; 109import android.widget.AdapterView.OnItemClickListener; 110import android.widget.ArrayAdapter; 111import android.widget.CheckedTextView; 112import android.widget.LinearLayout; 113import android.widget.ListView; 114import android.widget.OverScroller; 115import android.widget.PopupWindow; 116import android.widget.TextView; 117import android.widget.Toast; 118 119import junit.framework.Assert; 120 121import java.io.File; 122import java.io.FileInputStream; 123import java.io.FileNotFoundException; 124import java.io.FileOutputStream; 125import java.io.IOException; 126import java.io.InputStream; 127import java.io.OutputStream; 128import java.net.URLDecoder; 129import java.util.ArrayList; 130import java.util.HashMap; 131import java.util.HashSet; 132import java.util.List; 133import java.util.Map; 134import java.util.Set; 135import java.util.Vector; 136import java.util.regex.Matcher; 137import java.util.regex.Pattern; 138 139/** 140 * <p>A View that displays web pages. This class is the basis upon which you 141 * can roll your own web browser or simply display some online content within your Activity. 142 * It uses the WebKit rendering engine to display 143 * web pages and includes methods to navigate forward and backward 144 * through a history, zoom in and out, perform text searches and more.</p> 145 * <p>To enable the built-in zoom, set 146 * {@link #getSettings() WebSettings}.{@link WebSettings#setBuiltInZoomControls(boolean)} 147 * (introduced in API version 3). 148 * <p>Note that, in order for your Activity to access the Internet and load web pages 149 * in a WebView, you must add the {@code INTERNET} permissions to your 150 * Android Manifest file:</p> 151 * <pre><uses-permission android:name="android.permission.INTERNET" /></pre> 152 * 153 * <p>This must be a child of the <a 154 * href="{@docRoot}guide/topics/manifest/manifest-element.html">{@code <manifest>}</a> 155 * element.</p> 156 * 157 * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-webview.html">Web View 158 * tutorial</a>.</p> 159 * 160 * <h3>Basic usage</h3> 161 * 162 * <p>By default, a WebView provides no browser-like widgets, does not 163 * enable JavaScript and web page errors are ignored. If your goal is only 164 * to display some HTML as a part of your UI, this is probably fine; 165 * the user won't need to interact with the web page beyond reading 166 * it, and the web page won't need to interact with the user. If you 167 * actually want a full-blown web browser, then you probably want to 168 * invoke the Browser application with a URL Intent rather than show it 169 * with a WebView. For example: 170 * <pre> 171 * Uri uri = Uri.parse("http://www.example.com"); 172 * Intent intent = new Intent(Intent.ACTION_VIEW, uri); 173 * startActivity(intent); 174 * </pre> 175 * <p>See {@link android.content.Intent} for more information.</p> 176 * 177 * <p>To provide a WebView in your own Activity, include a {@code <WebView>} in your layout, 178 * or set the entire Activity window as a WebView during {@link 179 * android.app.Activity#onCreate(Bundle) onCreate()}:</p> 180 * <pre class="prettyprint"> 181 * WebView webview = new WebView(this); 182 * setContentView(webview); 183 * </pre> 184 * 185 * <p>Then load the desired web page:</p> 186 * <pre> 187 * // Simplest usage: note that an exception will NOT be thrown 188 * // if there is an error loading this page (see below). 189 * webview.loadUrl("http://slashdot.org/"); 190 * 191 * // OR, you can also load from an HTML string: 192 * String summary = "<html><body>You scored <b>192</b> points.</body></html>"; 193 * webview.loadData(summary, "text/html", null); 194 * // ... although note that there are restrictions on what this HTML can do. 195 * // See the JavaDocs for {@link #loadData(String,String,String) loadData()} and {@link 196 * #loadDataWithBaseURL(String,String,String,String,String) loadDataWithBaseURL()} for more info. 197 * </pre> 198 * 199 * <p>A WebView has several customization points where you can add your 200 * own behavior. These are:</p> 201 * 202 * <ul> 203 * <li>Creating and setting a {@link android.webkit.WebChromeClient} subclass. 204 * This class is called when something that might impact a 205 * browser UI happens, for instance, progress updates and 206 * JavaScript alerts are sent here (see <a 207 * href="{@docRoot}guide/developing/debug-tasks.html#DebuggingWebPages">Debugging Tasks</a>). 208 * </li> 209 * <li>Creating and setting a {@link android.webkit.WebViewClient} subclass. 210 * It will be called when things happen that impact the 211 * rendering of the content, eg, errors or form submissions. You 212 * can also intercept URL loading here (via {@link 213 * android.webkit.WebViewClient#shouldOverrideUrlLoading(WebView,String) 214 * shouldOverrideUrlLoading()}).</li> 215 * <li>Modifying the {@link android.webkit.WebSettings}, such as 216 * enabling JavaScript with {@link android.webkit.WebSettings#setJavaScriptEnabled(boolean) 217 * setJavaScriptEnabled()}. </li> 218 * <li>Injecting Java objects into the WebView using the 219 * {@link android.webkit.WebView#addJavascriptInterface} method. This 220 * method allows you to inject Java objects into a page's JavaScript 221 * context, so that they can be accessed by JavaScript in the page.</li> 222 * </ul> 223 * 224 * <p>Here's a more complicated example, showing error handling, 225 * settings, and progress notification:</p> 226 * 227 * <pre class="prettyprint"> 228 * // Let's display the progress in the activity title bar, like the 229 * // browser app does. 230 * getWindow().requestFeature(Window.FEATURE_PROGRESS); 231 * 232 * webview.getSettings().setJavaScriptEnabled(true); 233 * 234 * final Activity activity = this; 235 * webview.setWebChromeClient(new WebChromeClient() { 236 * public void onProgressChanged(WebView view, int progress) { 237 * // Activities and WebViews measure progress with different scales. 238 * // The progress meter will automatically disappear when we reach 100% 239 * activity.setProgress(progress * 1000); 240 * } 241 * }); 242 * webview.setWebViewClient(new WebViewClient() { 243 * public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { 244 * Toast.makeText(activity, "Oh no! " + description, Toast.LENGTH_SHORT).show(); 245 * } 246 * }); 247 * 248 * webview.loadUrl("http://slashdot.org/"); 249 * </pre> 250 * 251 * <h3>Cookie and window management</h3> 252 * 253 * <p>For obvious security reasons, your application has its own 254 * cache, cookie store etc.—it does not share the Browser 255 * application's data. Cookies are managed on a separate thread, so 256 * operations like index building don't block the UI 257 * thread. Follow the instructions in {@link android.webkit.CookieSyncManager} 258 * if you want to use cookies in your application. 259 * </p> 260 * 261 * <p>By default, requests by the HTML to open new windows are 262 * ignored. This is true whether they be opened by JavaScript or by 263 * the target attribute on a link. You can customize your 264 * {@link WebChromeClient} to provide your own behaviour for opening multiple windows, 265 * and render them in whatever manner you want.</p> 266 * 267 * <p>The standard behavior for an Activity is to be destroyed and 268 * recreated when the device orientation or any other configuration changes. This will cause 269 * the WebView to reload the current page. If you don't want that, you 270 * can set your Activity to handle the {@code orientation} and {@code keyboardHidden} 271 * changes, and then just leave the WebView alone. It'll automatically 272 * re-orient itself as appropriate. Read <a 273 * href="{@docRoot}guide/topics/resources/runtime-changes.html">Handling Runtime Changes</a> for 274 * more information about how to handle configuration changes during runtime.</p> 275 * 276 * 277 * <h3>Building web pages to support different screen densities</h3> 278 * 279 * <p>The screen density of a device is based on the screen resolution. A screen with low density 280 * has fewer available pixels per inch, where a screen with high density 281 * has more — sometimes significantly more — pixels per inch. The density of a 282 * screen is important because, other things being equal, a UI element (such as a button) whose 283 * height and width are defined in terms of screen pixels will appear larger on the lower density 284 * screen and smaller on the higher density screen. 285 * For simplicity, Android collapses all actual screen densities into three generalized densities: 286 * high, medium, and low.</p> 287 * <p>By default, WebView scales a web page so that it is drawn at a size that matches the default 288 * appearance on a medium density screen. So, it applies 1.5x scaling on a high density screen 289 * (because its pixels are smaller) and 0.75x scaling on a low density screen (because its pixels 290 * are bigger). 291 * Starting with API Level 5 (Android 2.0), WebView supports DOM, CSS, and meta tag features to help 292 * you (as a web developer) target screens with different screen densities.</p> 293 * <p>Here's a summary of the features you can use to handle different screen densities:</p> 294 * <ul> 295 * <li>The {@code window.devicePixelRatio} DOM property. The value of this property specifies the 296 * default scaling factor used for the current device. For example, if the value of {@code 297 * window.devicePixelRatio} is "1.0", then the device is considered a medium density (mdpi) device 298 * and default scaling is not applied to the web page; if the value is "1.5", then the device is 299 * considered a high density device (hdpi) and the page content is scaled 1.5x; if the 300 * value is "0.75", then the device is considered a low density device (ldpi) and the content is 301 * scaled 0.75x. However, if you specify the {@code "target-densitydpi"} meta property 302 * (discussed below), then you can stop this default scaling behavior.</li> 303 * <li>The {@code -webkit-device-pixel-ratio} CSS media query. Use this to specify the screen 304 * densities for which this style sheet is to be used. The corresponding value should be either 305 * "0.75", "1", or "1.5", to indicate that the styles are for devices with low density, medium 306 * density, or high density screens, respectively. For example: 307 * <pre> 308 * <link rel="stylesheet" media="screen and (-webkit-device-pixel-ratio:1.5)" href="hdpi.css" /></pre> 309 * <p>The {@code hdpi.css} stylesheet is only used for devices with a screen pixel ration of 1.5, 310 * which is the high density pixel ratio.</p> 311 * </li> 312 * <li>The {@code target-densitydpi} property for the {@code viewport} meta tag. You can use 313 * this to specify the target density for which the web page is designed, using the following 314 * values: 315 * <ul> 316 * <li>{@code device-dpi} - Use the device's native dpi as the target dpi. Default scaling never 317 * occurs.</li> 318 * <li>{@code high-dpi} - Use hdpi as the target dpi. Medium and low density screens scale down 319 * as appropriate.</li> 320 * <li>{@code medium-dpi} - Use mdpi as the target dpi. High density screens scale up and 321 * low density screens scale down. This is also the default behavior.</li> 322 * <li>{@code low-dpi} - Use ldpi as the target dpi. Medium and high density screens scale up 323 * as appropriate.</li> 324 * <li><em>{@code <value>}</em> - Specify a dpi value to use as the target dpi (accepted 325 * values are 70-400).</li> 326 * </ul> 327 * <p>Here's an example meta tag to specify the target density:</p> 328 * <pre><meta name="viewport" content="target-densitydpi=device-dpi" /></pre></li> 329 * </ul> 330 * <p>If you want to modify your web page for different densities, by using the {@code 331 * -webkit-device-pixel-ratio} CSS media query and/or the {@code 332 * window.devicePixelRatio} DOM property, then you should set the {@code target-densitydpi} meta 333 * property to {@code device-dpi}. This stops Android from performing scaling in your web page and 334 * allows you to make the necessary adjustments for each density via CSS and JavaScript.</p> 335 * 336 * <h3>HTML5 Video support</h3> 337 * 338 * <p>In order to support inline HTML5 video in your application, you need to have hardware 339 * acceleration turned on, and set a {@link android.webkit.WebChromeClient}. For full screen support, 340 * implementations of {@link WebChromeClient#onShowCustomView(View, WebChromeClient.CustomViewCallback)} 341 * and {@link WebChromeClient#onHideCustomView()} are required, 342 * {@link WebChromeClient#getVideoLoadingProgressView()} is optional. 343 * </p> 344 * 345 * 346 */ 347@Widget 348public class WebView extends AbsoluteLayout 349 implements ViewTreeObserver.OnGlobalFocusChangeListener, 350 ViewGroup.OnHierarchyChangeListener { 351 352 private class InnerGlobalLayoutListener implements ViewTreeObserver.OnGlobalLayoutListener { 353 @Override 354 public void onGlobalLayout() { 355 if (isShown()) { 356 setGLRectViewport(); 357 } 358 } 359 } 360 361 private class InnerScrollChangedListener implements ViewTreeObserver.OnScrollChangedListener { 362 @Override 363 public void onScrollChanged() { 364 if (isShown()) { 365 setGLRectViewport(); 366 } 367 } 368 } 369 370 /** 371 * InputConnection used for ContentEditable. This captures changes 372 * to the text and sends them either as key strokes or text changes. 373 */ 374 private class WebViewInputConnection extends BaseInputConnection { 375 // Used for mapping characters to keys typed. 376 private KeyCharacterMap mKeyCharacterMap; 377 private boolean mIsKeySentByMe; 378 private int mInputType; 379 private int mImeOptions; 380 private String mHint; 381 382 public WebViewInputConnection() { 383 super(WebView.this, true); 384 } 385 386 @Override 387 public boolean sendKeyEvent(KeyEvent event) { 388 // Some IMEs send key events directly using sendKeyEvents. 389 // WebViewInputConnection should treat these as text changes. 390 if (!mIsKeySentByMe) { 391 if (event.getAction() == KeyEvent.ACTION_UP) { 392 if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) { 393 return deleteSurroundingText(1, 0); 394 } else if (event.getKeyCode() == KeyEvent.KEYCODE_FORWARD_DEL) { 395 return deleteSurroundingText(0, 1); 396 } else if (event.getUnicodeChar() != 0){ 397 String newComposingText = 398 Character.toString((char)event.getUnicodeChar()); 399 return commitText(newComposingText, 1); 400 } 401 } else if (event.getAction() == KeyEvent.ACTION_DOWN && 402 (event.getKeyCode() == KeyEvent.KEYCODE_DEL 403 || event.getKeyCode() == KeyEvent.KEYCODE_FORWARD_DEL 404 || event.getUnicodeChar() != 0)) { 405 return true; // only act on action_down 406 } 407 } 408 return super.sendKeyEvent(event); 409 } 410 411 public void setTextAndKeepSelection(CharSequence text) { 412 Editable editable = getEditable(); 413 int selectionStart = Selection.getSelectionStart(editable); 414 int selectionEnd = Selection.getSelectionEnd(editable); 415 editable.replace(0, editable.length(), text); 416 InputMethodManager imm = InputMethodManager.peekInstance(); 417 if (imm != null) { 418 // Since the text has changed, do not allow the IME to replace the 419 // existing text as though it were a completion. 420 imm.restartInput(WebView.this); 421 } 422 // Keep the previous selection. 423 selectionStart = Math.min(selectionStart, editable.length()); 424 selectionEnd = Math.min(selectionEnd, editable.length()); 425 setSelection(selectionStart, selectionEnd); 426 } 427 428 public void replaceSelection(CharSequence text) { 429 Editable editable = getEditable(); 430 int selectionStart = Selection.getSelectionStart(editable); 431 int selectionEnd = Selection.getSelectionEnd(editable); 432 setNewText(selectionStart, selectionEnd, text); 433 editable.replace(selectionStart, selectionEnd, text); 434 InputMethodManager imm = InputMethodManager.peekInstance(); 435 if (imm != null) { 436 // Since the text has changed, do not allow the IME to replace the 437 // existing text as though it were a completion. 438 imm.restartInput(WebView.this); 439 } 440 // Move caret to the end of the new text 441 int newCaret = selectionStart + text.length(); 442 setSelection(newCaret, newCaret); 443 } 444 445 @Override 446 public boolean setComposingText(CharSequence text, int newCursorPosition) { 447 Editable editable = getEditable(); 448 int start = getComposingSpanStart(editable); 449 int end = getComposingSpanEnd(editable); 450 if (start < 0 || end < 0) { 451 start = Selection.getSelectionStart(editable); 452 end = Selection.getSelectionEnd(editable); 453 } 454 if (end < start) { 455 int temp = end; 456 end = start; 457 start = temp; 458 } 459 setNewText(start, end, text); 460 return super.setComposingText(text, newCursorPosition); 461 } 462 463 @Override 464 public boolean commitText(CharSequence text, int newCursorPosition) { 465 setComposingText(text, newCursorPosition); 466 int cursorPosition = Selection.getSelectionEnd(getEditable()); 467 setComposingRegion(cursorPosition, cursorPosition); 468 return true; 469 } 470 471 @Override 472 public boolean deleteSurroundingText(int leftLength, int rightLength) { 473 Editable editable = getEditable(); 474 int cursorPosition = Selection.getSelectionEnd(editable); 475 int startDelete = Math.max(0, cursorPosition - leftLength); 476 int endDelete = Math.min(editable.length(), 477 cursorPosition + rightLength); 478 setNewText(startDelete, endDelete, ""); 479 return super.deleteSurroundingText(leftLength, rightLength); 480 } 481 482 @Override 483 public boolean performEditorAction(int editorAction) { 484 485 boolean handled = true; 486 switch (editorAction) { 487 case EditorInfo.IME_ACTION_NEXT: 488 WebView.this.requestFocus(FOCUS_FORWARD); 489 break; 490 case EditorInfo.IME_ACTION_PREVIOUS: 491 WebView.this.requestFocus(FOCUS_BACKWARD); 492 break; 493 case EditorInfo.IME_ACTION_DONE: 494 WebView.this.hideSoftKeyboard(); 495 break; 496 case EditorInfo.IME_ACTION_GO: 497 case EditorInfo.IME_ACTION_SEARCH: 498 WebView.this.hideSoftKeyboard(); 499 String text = getEditable().toString(); 500 passToJavaScript(text, new KeyEvent(KeyEvent.ACTION_DOWN, 501 KeyEvent.KEYCODE_ENTER)); 502 passToJavaScript(text, new KeyEvent(KeyEvent.ACTION_UP, 503 KeyEvent.KEYCODE_ENTER)); 504 break; 505 506 default: 507 handled = super.performEditorAction(editorAction); 508 break; 509 } 510 511 return handled; 512 } 513 514 public void initEditorInfo(WebViewCore.TextFieldInitData initData) { 515 int type = initData.mType; 516 int inputType = InputType.TYPE_CLASS_TEXT 517 | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT; 518 int imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI 519 | EditorInfo.IME_FLAG_NO_FULLSCREEN; 520 if (!initData.mIsSpellCheckEnabled) { 521 inputType |= InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS; 522 } 523 if (WebTextView.TEXT_AREA != type 524 && initData.mIsTextFieldNext) { 525 imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT; 526 } 527 switch (type) { 528 case WebTextView.NORMAL_TEXT_FIELD: 529 imeOptions |= EditorInfo.IME_ACTION_GO; 530 break; 531 case WebTextView.TEXT_AREA: 532 inputType |= InputType.TYPE_TEXT_FLAG_MULTI_LINE 533 | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES 534 | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT; 535 imeOptions |= EditorInfo.IME_ACTION_NONE; 536 break; 537 case WebTextView.PASSWORD: 538 inputType |= EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD; 539 imeOptions |= EditorInfo.IME_ACTION_GO; 540 break; 541 case WebTextView.SEARCH: 542 imeOptions |= EditorInfo.IME_ACTION_SEARCH; 543 break; 544 case WebTextView.EMAIL: 545 // inputType needs to be overwritten because of the different text variation. 546 inputType = InputType.TYPE_CLASS_TEXT 547 | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS; 548 imeOptions |= EditorInfo.IME_ACTION_GO; 549 break; 550 case WebTextView.NUMBER: 551 // inputType needs to be overwritten because of the different class. 552 inputType = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_NORMAL 553 | InputType.TYPE_NUMBER_FLAG_SIGNED | InputType.TYPE_NUMBER_FLAG_DECIMAL; 554 // Number and telephone do not have both a Tab key and an 555 // action, so set the action to NEXT 556 imeOptions |= EditorInfo.IME_ACTION_NEXT; 557 break; 558 case WebTextView.TELEPHONE: 559 // inputType needs to be overwritten because of the different class. 560 inputType = InputType.TYPE_CLASS_PHONE; 561 imeOptions |= EditorInfo.IME_ACTION_NEXT; 562 break; 563 case WebTextView.URL: 564 // TYPE_TEXT_VARIATION_URI prevents Tab key from showing, so 565 // exclude it for now. 566 imeOptions |= EditorInfo.IME_ACTION_GO; 567 inputType |= InputType.TYPE_TEXT_VARIATION_URI; 568 break; 569 default: 570 imeOptions |= EditorInfo.IME_ACTION_GO; 571 break; 572 } 573 mHint = initData.mLabel; 574 mInputType = inputType; 575 mImeOptions = imeOptions; 576 } 577 578 public void setupEditorInfo(EditorInfo outAttrs) { 579 outAttrs.inputType = mInputType; 580 outAttrs.imeOptions = mImeOptions; 581 outAttrs.hintText = mHint; 582 outAttrs.initialCapsMode = getCursorCapsMode(InputType.TYPE_CLASS_TEXT); 583 } 584 585 /** 586 * Sends a text change to webkit indirectly. If it is a single- 587 * character add or delete, it sends it as a key stroke. If it cannot 588 * be represented as a key stroke, it sends it as a field change. 589 * @param start The start offset (inclusive) of the text being changed. 590 * @param end The end offset (exclusive) of the text being changed. 591 * @param text The new text to replace the changed text. 592 */ 593 private void setNewText(int start, int end, CharSequence text) { 594 mIsKeySentByMe = true; 595 Editable editable = getEditable(); 596 CharSequence original = editable.subSequence(start, end); 597 boolean isCharacterAdd = false; 598 boolean isCharacterDelete = false; 599 int textLength = text.length(); 600 int originalLength = original.length(); 601 if (textLength > originalLength) { 602 isCharacterAdd = (textLength == originalLength + 1) 603 && TextUtils.regionMatches(text, 0, original, 0, 604 originalLength); 605 } else if (originalLength > textLength) { 606 isCharacterDelete = (textLength == originalLength - 1) 607 && TextUtils.regionMatches(text, 0, original, 0, 608 textLength); 609 } 610 if (isCharacterAdd) { 611 sendCharacter(text.charAt(textLength - 1)); 612 } else if (isCharacterDelete) { 613 sendKey(KeyEvent.KEYCODE_DEL); 614 } else if ((textLength != originalLength) || 615 !TextUtils.regionMatches(text, 0, original, 0, 616 textLength)) { 617 // Send a message so that key strokes and text replacement 618 // do not come out of order. 619 Message replaceMessage = mPrivateHandler.obtainMessage( 620 REPLACE_TEXT, start, end, text.toString()); 621 mPrivateHandler.sendMessage(replaceMessage); 622 } 623 mIsKeySentByMe = false; 624 } 625 626 /** 627 * Send a single character to the WebView as a key down and up event. 628 * @param c The character to be sent. 629 */ 630 private void sendCharacter(char c) { 631 if (mKeyCharacterMap == null) { 632 mKeyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); 633 } 634 char[] chars = new char[1]; 635 chars[0] = c; 636 KeyEvent[] events = mKeyCharacterMap.getEvents(chars); 637 if (events != null) { 638 for (KeyEvent event : events) { 639 sendKeyEvent(event); 640 } 641 } else { 642 Message msg = mPrivateHandler.obtainMessage(KEY_PRESS, (int) c, 0); 643 mPrivateHandler.sendMessage(msg); 644 } 645 } 646 647 /** 648 * Send a key event for a specific key code, not a standard 649 * unicode character. 650 * @param keyCode The key code to send. 651 */ 652 private void sendKey(int keyCode) { 653 long eventTime = SystemClock.uptimeMillis(); 654 sendKeyEvent(new KeyEvent(eventTime, eventTime, 655 KeyEvent.ACTION_DOWN, keyCode, 0, 0, 656 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 657 KeyEvent.FLAG_SOFT_KEYBOARD)); 658 sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime, 659 KeyEvent.ACTION_UP, keyCode, 0, 0, 660 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 661 KeyEvent.FLAG_SOFT_KEYBOARD)); 662 } 663 } 664 665 private class PastePopupWindow extends PopupWindow implements OnClickListener { 666 private ViewGroup mContentView; 667 private TextView mPasteTextView; 668 669 public PastePopupWindow() { 670 super(WebView.this.mContext, null, 671 com.android.internal.R.attr.textSelectHandleWindowStyle); 672 setClippingEnabled(true); 673 LinearLayout linearLayout = new LinearLayout(WebView.this.getContext()); 674 linearLayout.setOrientation(LinearLayout.HORIZONTAL); 675 mContentView = linearLayout; 676 mContentView.setBackgroundResource( 677 com.android.internal.R.drawable.text_edit_paste_window); 678 679 LayoutInflater inflater = (LayoutInflater)WebView.this.mContext. 680 getSystemService(Context.LAYOUT_INFLATER_SERVICE); 681 682 ViewGroup.LayoutParams wrapContent = new ViewGroup.LayoutParams( 683 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 684 685 mPasteTextView = (TextView) inflater.inflate( 686 com.android.internal.R.layout.text_edit_action_popup_text, null); 687 mPasteTextView.setLayoutParams(wrapContent); 688 mContentView.addView(mPasteTextView); 689 mPasteTextView.setText(com.android.internal.R.string.paste); 690 mPasteTextView.setOnClickListener(this); 691 this.setContentView(mContentView); 692 } 693 694 public void show(Rect cursorRect, int windowLeft, int windowTop) { 695 measureContent(); 696 697 int width = mContentView.getMeasuredWidth(); 698 int height = mContentView.getMeasuredHeight(); 699 int y = cursorRect.top - height; 700 if (y < windowTop) { 701 // There's not enough room vertically, move it below the 702 // handle. 703 // The selection handle is vertically offset by 1/4 of the 704 // line height. 705 y = cursorRect.bottom - (cursorRect.height() / 4) + 706 mSelectHandleCenter.getIntrinsicHeight(); 707 } 708 int x = cursorRect.centerX() - (width / 2); 709 if (x < windowLeft) { 710 x = windowLeft; 711 } 712 if (!isShowing()) { 713 showAtLocation(WebView.this, Gravity.NO_GRAVITY, x, y); 714 } 715 update(x, y, width, height); 716 } 717 718 public void hide() { 719 dismiss(); 720 } 721 722 @Override 723 public void onClick(View view) { 724 pasteFromClipboard(); 725 selectionDone(); 726 } 727 728 protected void measureContent() { 729 final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics(); 730 mContentView.measure( 731 View.MeasureSpec.makeMeasureSpec(displayMetrics.widthPixels, 732 View.MeasureSpec.AT_MOST), 733 View.MeasureSpec.makeMeasureSpec(displayMetrics.heightPixels, 734 View.MeasureSpec.AT_MOST)); 735 } 736 } 737 738 // The listener to capture global layout change event. 739 private InnerGlobalLayoutListener mGlobalLayoutListener = null; 740 741 // The listener to capture scroll event. 742 private InnerScrollChangedListener mScrollChangedListener = null; 743 744 // if AUTO_REDRAW_HACK is true, then the CALL key will toggle redrawing 745 // the screen all-the-time. Good for profiling our drawing code 746 static private final boolean AUTO_REDRAW_HACK = false; 747 // true means redraw the screen all-the-time. Only with AUTO_REDRAW_HACK 748 private boolean mAutoRedraw; 749 750 // Reference to the AlertDialog displayed by InvokeListBox. 751 // It's used to dismiss the dialog in destroy if not done before. 752 private AlertDialog mListBoxDialog = null; 753 754 static final String LOGTAG = "webview"; 755 756 private ZoomManager mZoomManager; 757 758 private final Rect mGLRectViewport = new Rect(); 759 private final Rect mViewRectViewport = new Rect(); 760 private final RectF mVisibleContentRect = new RectF(); 761 private boolean mGLViewportEmpty = false; 762 WebViewInputConnection mInputConnection = null; 763 private int mFieldPointer; 764 private PastePopupWindow mPasteWindow; 765 766 /** 767 * Transportation object for returning WebView across thread boundaries. 768 */ 769 public class WebViewTransport { 770 private WebView mWebview; 771 772 /** 773 * Set the WebView to the transportation object. 774 * @param webview The WebView to transport. 775 */ 776 public synchronized void setWebView(WebView webview) { 777 mWebview = webview; 778 } 779 780 /** 781 * Return the WebView object. 782 * @return WebView The transported WebView object. 783 */ 784 public synchronized WebView getWebView() { 785 return mWebview; 786 } 787 } 788 789 private static class OnTrimMemoryListener implements ComponentCallbacks2 { 790 private static OnTrimMemoryListener sInstance = null; 791 792 static void init(Context c) { 793 if (sInstance == null) { 794 sInstance = new OnTrimMemoryListener(c.getApplicationContext()); 795 } 796 } 797 798 private OnTrimMemoryListener(Context c) { 799 c.registerComponentCallbacks(this); 800 } 801 802 @Override 803 public void onConfigurationChanged(Configuration newConfig) { 804 // Ignore 805 } 806 807 @Override 808 public void onLowMemory() { 809 // Ignore 810 } 811 812 @Override 813 public void onTrimMemory(int level) { 814 if (DebugFlags.WEB_VIEW) { 815 Log.d("WebView", "onTrimMemory: " + level); 816 } 817 WebView.nativeOnTrimMemory(level); 818 } 819 820 } 821 822 // A final CallbackProxy shared by WebViewCore and BrowserFrame. 823 private final CallbackProxy mCallbackProxy; 824 825 private final WebViewDatabase mDatabase; 826 827 // SSL certificate for the main top-level page (if secure) 828 private SslCertificate mCertificate; 829 830 // Native WebView pointer that is 0 until the native object has been 831 // created. 832 private int mNativeClass; 833 // This would be final but it needs to be set to null when the WebView is 834 // destroyed. 835 private WebViewCore mWebViewCore; 836 // Handler for dispatching UI messages. 837 /* package */ final Handler mPrivateHandler = new PrivateHandler(); 838 private WebTextView mWebTextView; 839 // Used to ignore changes to webkit text that arrives to the UI side after 840 // more key events. 841 private int mTextGeneration; 842 843 /* package */ void incrementTextGeneration() { mTextGeneration++; } 844 845 // Used by WebViewCore to create child views. 846 /* package */ final ViewManager mViewManager; 847 848 // Used to display in full screen mode 849 PluginFullScreenHolder mFullScreenHolder; 850 851 /** 852 * Position of the last touch event in pixels. 853 * Use integer to prevent loss of dragging delta calculation accuracy; 854 * which was done in float and converted to integer, and resulted in gradual 855 * and compounding touch position and view dragging mismatch. 856 */ 857 private int mLastTouchX; 858 private int mLastTouchY; 859 private int mStartTouchX; 860 private int mStartTouchY; 861 private float mAverageAngle; 862 863 /** 864 * Time of the last touch event. 865 */ 866 private long mLastTouchTime; 867 868 /** 869 * Time of the last time sending touch event to WebViewCore 870 */ 871 private long mLastSentTouchTime; 872 873 /** 874 * The minimum elapsed time before sending another ACTION_MOVE event to 875 * WebViewCore. This really should be tuned for each type of the devices. 876 * For example in Google Map api test case, it takes Dream device at least 877 * 150ms to do a full cycle in the WebViewCore by processing a touch event, 878 * triggering the layout and drawing the picture. While the same process 879 * takes 60+ms on the current high speed device. If we make 880 * TOUCH_SENT_INTERVAL too small, there will be multiple touch events sent 881 * to WebViewCore queue and the real layout and draw events will be pushed 882 * to further, which slows down the refresh rate. Choose 50 to favor the 883 * current high speed devices. For Dream like devices, 100 is a better 884 * choice. Maybe make this in the buildspec later. 885 * (Update 12/14/2010: changed to 0 since current device should be able to 886 * handle the raw events and Map team voted to have the raw events too. 887 */ 888 private static final int TOUCH_SENT_INTERVAL = 0; 889 private int mCurrentTouchInterval = TOUCH_SENT_INTERVAL; 890 891 /** 892 * Helper class to get velocity for fling 893 */ 894 VelocityTracker mVelocityTracker; 895 private int mMaximumFling; 896 private float mLastVelocity; 897 private float mLastVelX; 898 private float mLastVelY; 899 900 // The id of the native layer being scrolled. 901 private int mCurrentScrollingLayerId; 902 private Rect mScrollingLayerRect = new Rect(); 903 904 // only trigger accelerated fling if the new velocity is at least 905 // MINIMUM_VELOCITY_RATIO_FOR_ACCELERATION times of the previous velocity 906 private static final float MINIMUM_VELOCITY_RATIO_FOR_ACCELERATION = 0.2f; 907 908 /** 909 * Touch mode 910 */ 911 private int mTouchMode = TOUCH_DONE_MODE; 912 private static final int TOUCH_INIT_MODE = 1; 913 private static final int TOUCH_DRAG_START_MODE = 2; 914 private static final int TOUCH_DRAG_MODE = 3; 915 private static final int TOUCH_SHORTPRESS_START_MODE = 4; 916 private static final int TOUCH_SHORTPRESS_MODE = 5; 917 private static final int TOUCH_DOUBLE_TAP_MODE = 6; 918 private static final int TOUCH_DONE_MODE = 7; 919 private static final int TOUCH_PINCH_DRAG = 8; 920 private static final int TOUCH_DRAG_LAYER_MODE = 9; 921 922 // Whether to forward the touch events to WebCore 923 // Can only be set by WebKit via JNI. 924 private boolean mForwardTouchEvents = false; 925 926 // Whether to prevent default during touch. The initial value depends on 927 // mForwardTouchEvents. If WebCore wants all the touch events, it says yes 928 // for touch down. Otherwise UI will wait for the answer of the first 929 // confirmed move before taking over the control. 930 private static final int PREVENT_DEFAULT_NO = 0; 931 private static final int PREVENT_DEFAULT_MAYBE_YES = 1; 932 private static final int PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN = 2; 933 private static final int PREVENT_DEFAULT_YES = 3; 934 private static final int PREVENT_DEFAULT_IGNORE = 4; 935 private int mPreventDefault = PREVENT_DEFAULT_IGNORE; 936 937 // true when the touch movement exceeds the slop 938 private boolean mConfirmMove; 939 940 // if true, touch events will be first processed by WebCore, if prevent 941 // default is not set, the UI will continue handle them. 942 private boolean mDeferTouchProcess; 943 944 // to avoid interfering with the current touch events, track them 945 // separately. Currently no snapping or fling in the deferred process mode 946 private int mDeferTouchMode = TOUCH_DONE_MODE; 947 private float mLastDeferTouchX; 948 private float mLastDeferTouchY; 949 950 // To keep track of whether the current drag was initiated by a WebTextView, 951 // so that we know not to hide the cursor 952 boolean mDragFromTextInput; 953 954 // Whether or not to draw the cursor ring. 955 private boolean mDrawCursorRing = true; 956 957 // true if onPause has been called (and not onResume) 958 private boolean mIsPaused; 959 960 private HitTestResult mInitialHitTestResult; 961 private WebKitHitTest mFocusedNode; 962 963 /** 964 * Customizable constant 965 */ 966 // pre-computed square of ViewConfiguration.getScaledTouchSlop() 967 private int mTouchSlopSquare; 968 // pre-computed square of ViewConfiguration.getScaledDoubleTapSlop() 969 private int mDoubleTapSlopSquare; 970 // pre-computed density adjusted navigation slop 971 private int mNavSlop; 972 // This should be ViewConfiguration.getTapTimeout() 973 // But system time out is 100ms, which is too short for the browser. 974 // In the browser, if it switches out of tap too soon, jump tap won't work. 975 // In addition, a double tap on a trackpad will always have a duration of 976 // 300ms, so this value must be at least that (otherwise we will timeout the 977 // first tap and convert it to a long press). 978 private static final int TAP_TIMEOUT = 300; 979 // This should be ViewConfiguration.getLongPressTimeout() 980 // But system time out is 500ms, which is too short for the browser. 981 // With a short timeout, it's difficult to treat trigger a short press. 982 private static final int LONG_PRESS_TIMEOUT = 1000; 983 // needed to avoid flinging after a pause of no movement 984 private static final int MIN_FLING_TIME = 250; 985 // draw unfiltered after drag is held without movement 986 private static final int MOTIONLESS_TIME = 100; 987 // The amount of content to overlap between two screens when going through 988 // pages with the space bar, in pixels. 989 private static final int PAGE_SCROLL_OVERLAP = 24; 990 991 /** 992 * These prevent calling requestLayout if either dimension is fixed. This 993 * depends on the layout parameters and the measure specs. 994 */ 995 boolean mWidthCanMeasure; 996 boolean mHeightCanMeasure; 997 998 // Remember the last dimensions we sent to the native side so we can avoid 999 // sending the same dimensions more than once. 1000 int mLastWidthSent; 1001 int mLastHeightSent; 1002 // Since view height sent to webkit could be fixed to avoid relayout, this 1003 // value records the last sent actual view height. 1004 int mLastActualHeightSent; 1005 1006 private int mContentWidth; // cache of value from WebViewCore 1007 private int mContentHeight; // cache of value from WebViewCore 1008 1009 // Need to have the separate control for horizontal and vertical scrollbar 1010 // style than the View's single scrollbar style 1011 private boolean mOverlayHorizontalScrollbar = true; 1012 private boolean mOverlayVerticalScrollbar = false; 1013 1014 // our standard speed. this way small distances will be traversed in less 1015 // time than large distances, but we cap the duration, so that very large 1016 // distances won't take too long to get there. 1017 private static final int STD_SPEED = 480; // pixels per second 1018 // time for the longest scroll animation 1019 private static final int MAX_DURATION = 750; // milliseconds 1020 private static final int SLIDE_TITLE_DURATION = 500; // milliseconds 1021 1022 // Used by OverScrollGlow 1023 OverScroller mScroller; 1024 1025 private boolean mInOverScrollMode = false; 1026 private static Paint mOverScrollBackground; 1027 private static Paint mOverScrollBorder; 1028 1029 private boolean mWrapContent; 1030 private static final int MOTIONLESS_FALSE = 0; 1031 private static final int MOTIONLESS_PENDING = 1; 1032 private static final int MOTIONLESS_TRUE = 2; 1033 private static final int MOTIONLESS_IGNORE = 3; 1034 private int mHeldMotionless; 1035 1036 // An instance for injecting accessibility in WebViews with disabled 1037 // JavaScript or ones for which no accessibility script exists 1038 private AccessibilityInjector mAccessibilityInjector; 1039 1040 // flag indicating if accessibility script is injected so we 1041 // know to handle Shift and arrows natively first 1042 private boolean mAccessibilityScriptInjected; 1043 1044 1045 /** 1046 * How long the caret handle will last without being touched. 1047 */ 1048 private static final long CARET_HANDLE_STAMINA_MS = 3000; 1049 1050 private Drawable mSelectHandleLeft; 1051 private Drawable mSelectHandleRight; 1052 private Drawable mSelectHandleCenter; 1053 private Rect mSelectCursorBase = new Rect(); 1054 private int mSelectCursorBaseLayerId; 1055 private Rect mSelectCursorExtent = new Rect(); 1056 private int mSelectCursorExtentLayerId; 1057 private Rect mSelectDraggingCursor; 1058 private Point mSelectDraggingOffset = new Point(); 1059 private boolean mIsCaretSelection; 1060 static final int HANDLE_ID_START = 0; 1061 static final int HANDLE_ID_END = 1; 1062 static final int HANDLE_ID_BASE = 2; 1063 static final int HANDLE_ID_EXTENT = 3; 1064 1065 static boolean sDisableNavcache = false; 1066 static boolean sEnableWebTextView = false; 1067 // the color used to highlight the touch rectangles 1068 static final int HIGHLIGHT_COLOR = 0x6633b5e5; 1069 // the region indicating where the user touched on the screen 1070 private Region mTouchHighlightRegion = new Region(); 1071 // the paint for the touch highlight 1072 private Paint mTouchHightlightPaint = new Paint(); 1073 // debug only 1074 private static final boolean DEBUG_TOUCH_HIGHLIGHT = true; 1075 private static final int TOUCH_HIGHLIGHT_ELAPSE_TIME = 2000; 1076 private Paint mTouchCrossHairColor; 1077 private int mTouchHighlightX; 1078 private int mTouchHighlightY; 1079 private long mTouchHighlightRequested; 1080 1081 // Basically this proxy is used to tell the Video to update layer tree at 1082 // SetBaseLayer time and to pause when WebView paused. 1083 private HTML5VideoViewProxy mHTML5VideoViewProxy; 1084 1085 // If we are using a set picture, don't send view updates to webkit 1086 private boolean mBlockWebkitViewMessages = false; 1087 1088 // cached value used to determine if we need to switch drawing models 1089 private boolean mHardwareAccelSkia = false; 1090 1091 /* 1092 * Private message ids 1093 */ 1094 private static final int REMEMBER_PASSWORD = 1; 1095 private static final int NEVER_REMEMBER_PASSWORD = 2; 1096 private static final int SWITCH_TO_SHORTPRESS = 3; 1097 private static final int SWITCH_TO_LONGPRESS = 4; 1098 private static final int RELEASE_SINGLE_TAP = 5; 1099 private static final int REQUEST_FORM_DATA = 6; 1100 private static final int DRAG_HELD_MOTIONLESS = 8; 1101 private static final int AWAKEN_SCROLL_BARS = 9; 1102 private static final int PREVENT_DEFAULT_TIMEOUT = 10; 1103 private static final int SCROLL_SELECT_TEXT = 11; 1104 1105 1106 private static final int FIRST_PRIVATE_MSG_ID = REMEMBER_PASSWORD; 1107 private static final int LAST_PRIVATE_MSG_ID = SCROLL_SELECT_TEXT; 1108 1109 /* 1110 * Package message ids 1111 */ 1112 static final int SCROLL_TO_MSG_ID = 101; 1113 static final int NEW_PICTURE_MSG_ID = 105; 1114 static final int UPDATE_TEXT_ENTRY_MSG_ID = 106; 1115 static final int WEBCORE_INITIALIZED_MSG_ID = 107; 1116 static final int UPDATE_TEXTFIELD_TEXT_MSG_ID = 108; 1117 static final int UPDATE_ZOOM_RANGE = 109; 1118 static final int UNHANDLED_NAV_KEY = 110; 1119 static final int CLEAR_TEXT_ENTRY = 111; 1120 static final int UPDATE_TEXT_SELECTION_MSG_ID = 112; 1121 static final int SHOW_RECT_MSG_ID = 113; 1122 static final int LONG_PRESS_CENTER = 114; 1123 static final int PREVENT_TOUCH_ID = 115; 1124 static final int WEBCORE_NEED_TOUCH_EVENTS = 116; 1125 // obj=Rect in doc coordinates 1126 static final int INVAL_RECT_MSG_ID = 117; 1127 static final int REQUEST_KEYBOARD = 118; 1128 static final int DO_MOTION_UP = 119; 1129 static final int SHOW_FULLSCREEN = 120; 1130 static final int HIDE_FULLSCREEN = 121; 1131 static final int DOM_FOCUS_CHANGED = 122; 1132 static final int REPLACE_BASE_CONTENT = 123; 1133 static final int FORM_DID_BLUR = 124; 1134 static final int RETURN_LABEL = 125; 1135 static final int UPDATE_MATCH_COUNT = 126; 1136 static final int CENTER_FIT_RECT = 127; 1137 static final int REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID = 128; 1138 static final int SET_SCROLLBAR_MODES = 129; 1139 static final int SELECTION_STRING_CHANGED = 130; 1140 static final int HIT_TEST_RESULT = 131; 1141 static final int SAVE_WEBARCHIVE_FINISHED = 132; 1142 1143 static final int SET_AUTOFILLABLE = 133; 1144 static final int AUTOFILL_COMPLETE = 134; 1145 1146 static final int SELECT_AT = 135; 1147 static final int SCREEN_ON = 136; 1148 static final int ENTER_FULLSCREEN_VIDEO = 137; 1149 static final int UPDATE_SELECTION = 138; 1150 static final int UPDATE_ZOOM_DENSITY = 139; 1151 static final int EXIT_FULLSCREEN_VIDEO = 140; 1152 1153 static final int COPY_TO_CLIPBOARD = 141; 1154 static final int INIT_EDIT_FIELD = 142; 1155 static final int REPLACE_TEXT = 143; 1156 static final int CLEAR_CARET_HANDLE = 144; 1157 static final int KEY_PRESS = 145; 1158 1159 private static final int FIRST_PACKAGE_MSG_ID = SCROLL_TO_MSG_ID; 1160 private static final int LAST_PACKAGE_MSG_ID = HIT_TEST_RESULT; 1161 1162 static final String[] HandlerPrivateDebugString = { 1163 "REMEMBER_PASSWORD", // = 1; 1164 "NEVER_REMEMBER_PASSWORD", // = 2; 1165 "SWITCH_TO_SHORTPRESS", // = 3; 1166 "SWITCH_TO_LONGPRESS", // = 4; 1167 "RELEASE_SINGLE_TAP", // = 5; 1168 "REQUEST_FORM_DATA", // = 6; 1169 "RESUME_WEBCORE_PRIORITY", // = 7; 1170 "DRAG_HELD_MOTIONLESS", // = 8; 1171 "AWAKEN_SCROLL_BARS", // = 9; 1172 "PREVENT_DEFAULT_TIMEOUT", // = 10; 1173 "SCROLL_SELECT_TEXT" // = 11; 1174 }; 1175 1176 static final String[] HandlerPackageDebugString = { 1177 "SCROLL_TO_MSG_ID", // = 101; 1178 "102", // = 102; 1179 "103", // = 103; 1180 "104", // = 104; 1181 "NEW_PICTURE_MSG_ID", // = 105; 1182 "UPDATE_TEXT_ENTRY_MSG_ID", // = 106; 1183 "WEBCORE_INITIALIZED_MSG_ID", // = 107; 1184 "UPDATE_TEXTFIELD_TEXT_MSG_ID", // = 108; 1185 "UPDATE_ZOOM_RANGE", // = 109; 1186 "UNHANDLED_NAV_KEY", // = 110; 1187 "CLEAR_TEXT_ENTRY", // = 111; 1188 "UPDATE_TEXT_SELECTION_MSG_ID", // = 112; 1189 "SHOW_RECT_MSG_ID", // = 113; 1190 "LONG_PRESS_CENTER", // = 114; 1191 "PREVENT_TOUCH_ID", // = 115; 1192 "WEBCORE_NEED_TOUCH_EVENTS", // = 116; 1193 "INVAL_RECT_MSG_ID", // = 117; 1194 "REQUEST_KEYBOARD", // = 118; 1195 "DO_MOTION_UP", // = 119; 1196 "SHOW_FULLSCREEN", // = 120; 1197 "HIDE_FULLSCREEN", // = 121; 1198 "DOM_FOCUS_CHANGED", // = 122; 1199 "REPLACE_BASE_CONTENT", // = 123; 1200 "FORM_DID_BLUR", // = 124; 1201 "RETURN_LABEL", // = 125; 1202 "UPDATE_MATCH_COUNT", // = 126; 1203 "CENTER_FIT_RECT", // = 127; 1204 "REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID", // = 128; 1205 "SET_SCROLLBAR_MODES", // = 129; 1206 "SELECTION_STRING_CHANGED", // = 130; 1207 "SET_TOUCH_HIGHLIGHT_RECTS", // = 131; 1208 "SAVE_WEBARCHIVE_FINISHED", // = 132; 1209 "SET_AUTOFILLABLE", // = 133; 1210 "AUTOFILL_COMPLETE", // = 134; 1211 "SELECT_AT", // = 135; 1212 "SCREEN_ON", // = 136; 1213 "ENTER_FULLSCREEN_VIDEO", // = 137; 1214 "UPDATE_SELECTION", // = 138; 1215 "UPDATE_ZOOM_DENSITY" // = 139; 1216 }; 1217 1218 // If the site doesn't use the viewport meta tag to specify the viewport, 1219 // use DEFAULT_VIEWPORT_WIDTH as the default viewport width 1220 static final int DEFAULT_VIEWPORT_WIDTH = 980; 1221 1222 // normally we try to fit the content to the minimum preferred width 1223 // calculated by the Webkit. To avoid the bad behavior when some site's 1224 // minimum preferred width keeps growing when changing the viewport width or 1225 // the minimum preferred width is huge, an upper limit is needed. 1226 static int sMaxViewportWidth = DEFAULT_VIEWPORT_WIDTH; 1227 1228 // initial scale in percent. 0 means using default. 1229 private int mInitialScaleInPercent = 0; 1230 1231 // Whether or not a scroll event should be sent to webkit. This is only set 1232 // to false when restoring the scroll position. 1233 private boolean mSendScrollEvent = true; 1234 1235 private int mSnapScrollMode = SNAP_NONE; 1236 private static final int SNAP_NONE = 0; 1237 private static final int SNAP_LOCK = 1; // not a separate state 1238 private static final int SNAP_X = 2; // may be combined with SNAP_LOCK 1239 private static final int SNAP_Y = 4; // may be combined with SNAP_LOCK 1240 private boolean mSnapPositive; 1241 1242 // keep these in sync with their counterparts in WebView.cpp 1243 private static final int DRAW_EXTRAS_NONE = 0; 1244 private static final int DRAW_EXTRAS_SELECTION = 1; 1245 private static final int DRAW_EXTRAS_CURSOR_RING = 2; 1246 1247 // keep this in sync with WebCore:ScrollbarMode in WebKit 1248 private static final int SCROLLBAR_AUTO = 0; 1249 private static final int SCROLLBAR_ALWAYSOFF = 1; 1250 // as we auto fade scrollbar, this is ignored. 1251 private static final int SCROLLBAR_ALWAYSON = 2; 1252 private int mHorizontalScrollBarMode = SCROLLBAR_AUTO; 1253 private int mVerticalScrollBarMode = SCROLLBAR_AUTO; 1254 1255 // constants for determining script injection strategy 1256 private static final int ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED = -1; 1257 private static final int ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT = 0; 1258 private static final int ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED = 1; 1259 1260 // the alias via which accessibility JavaScript interface is exposed 1261 private static final String ALIAS_ACCESSIBILITY_JS_INTERFACE = "accessibility"; 1262 1263 // Template for JavaScript that injects a screen-reader. 1264 private static final String ACCESSIBILITY_SCREEN_READER_JAVASCRIPT_TEMPLATE = 1265 "javascript:(function() {" + 1266 " var chooser = document.createElement('script');" + 1267 " chooser.type = 'text/javascript';" + 1268 " chooser.src = '%1s';" + 1269 " document.getElementsByTagName('head')[0].appendChild(chooser);" + 1270 " })();"; 1271 1272 // Regular expression that matches the "axs" URL parameter. 1273 // The value of 0 means the accessibility script is opted out 1274 // The value of 1 means the accessibility script is already injected 1275 private static final String PATTERN_MATCH_AXS_URL_PARAMETER = "(\\?axs=(0|1))|(&axs=(0|1))"; 1276 1277 // TextToSpeech instance exposed to JavaScript to the injected screenreader. 1278 private TextToSpeech mTextToSpeech; 1279 1280 // variable to cache the above pattern in case accessibility is enabled. 1281 private Pattern mMatchAxsUrlParameterPattern; 1282 1283 /** 1284 * Max distance to overscroll by in pixels. 1285 * This how far content can be pulled beyond its normal bounds by the user. 1286 */ 1287 private int mOverscrollDistance; 1288 1289 /** 1290 * Max distance to overfling by in pixels. 1291 * This is how far flinged content can move beyond the end of its normal bounds. 1292 */ 1293 private int mOverflingDistance; 1294 1295 private OverScrollGlow mOverScrollGlow; 1296 1297 // Used to match key downs and key ups 1298 private Vector<Integer> mKeysPressed; 1299 1300 /* package */ static boolean mLogEvent = true; 1301 1302 // for event log 1303 private long mLastTouchUpTime = 0; 1304 1305 private WebViewCore.AutoFillData mAutoFillData; 1306 1307 private static boolean sNotificationsEnabled = true; 1308 1309 /** 1310 * URI scheme for telephone number 1311 */ 1312 public static final String SCHEME_TEL = "tel:"; 1313 /** 1314 * URI scheme for email address 1315 */ 1316 public static final String SCHEME_MAILTO = "mailto:"; 1317 /** 1318 * URI scheme for map address 1319 */ 1320 public static final String SCHEME_GEO = "geo:0,0?q="; 1321 1322 private int mBackgroundColor = Color.WHITE; 1323 1324 private static final long SELECT_SCROLL_INTERVAL = 1000 / 60; // 60 / second 1325 private int mAutoScrollX = 0; 1326 private int mAutoScrollY = 0; 1327 private int mMinAutoScrollX = 0; 1328 private int mMaxAutoScrollX = 0; 1329 private int mMinAutoScrollY = 0; 1330 private int mMaxAutoScrollY = 0; 1331 private Rect mScrollingLayerBounds = new Rect(); 1332 private boolean mSentAutoScrollMessage = false; 1333 1334 // used for serializing asynchronously handled touch events. 1335 private final TouchEventQueue mTouchEventQueue = new TouchEventQueue(); 1336 1337 // Used to track whether picture updating was paused due to a window focus change. 1338 private boolean mPictureUpdatePausedForFocusChange = false; 1339 1340 // Used to notify listeners of a new picture. 1341 private PictureListener mPictureListener; 1342 /** 1343 * Interface to listen for new pictures as they change. 1344 * @deprecated This interface is now obsolete. 1345 */ 1346 @Deprecated 1347 public interface PictureListener { 1348 /** 1349 * Notify the listener that the picture has changed. 1350 * @param view The WebView that owns the picture. 1351 * @param picture The new picture. 1352 * @deprecated Due to internal changes, the picture does not include 1353 * composited layers such as fixed position elements or scrollable divs. 1354 * While the PictureListener API can still be used to detect changes in 1355 * the WebView content, you are advised against its usage until a replacement 1356 * is provided in a future Android release 1357 */ 1358 @Deprecated 1359 public void onNewPicture(WebView view, Picture picture); 1360 } 1361 1362 public static class HitTestResult { 1363 /** 1364 * Default HitTestResult, where the target is unknown 1365 */ 1366 public static final int UNKNOWN_TYPE = 0; 1367 /** 1368 * @deprecated This type is no longer used. 1369 */ 1370 @Deprecated 1371 public static final int ANCHOR_TYPE = 1; 1372 /** 1373 * HitTestResult for hitting a phone number 1374 */ 1375 public static final int PHONE_TYPE = 2; 1376 /** 1377 * HitTestResult for hitting a map address 1378 */ 1379 public static final int GEO_TYPE = 3; 1380 /** 1381 * HitTestResult for hitting an email address 1382 */ 1383 public static final int EMAIL_TYPE = 4; 1384 /** 1385 * HitTestResult for hitting an HTML::img tag 1386 */ 1387 public static final int IMAGE_TYPE = 5; 1388 /** 1389 * @deprecated This type is no longer used. 1390 */ 1391 @Deprecated 1392 public static final int IMAGE_ANCHOR_TYPE = 6; 1393 /** 1394 * HitTestResult for hitting a HTML::a tag with src=http 1395 */ 1396 public static final int SRC_ANCHOR_TYPE = 7; 1397 /** 1398 * HitTestResult for hitting a HTML::a tag with src=http + HTML::img 1399 */ 1400 public static final int SRC_IMAGE_ANCHOR_TYPE = 8; 1401 /** 1402 * HitTestResult for hitting an edit text area 1403 */ 1404 public static final int EDIT_TEXT_TYPE = 9; 1405 1406 private int mType; 1407 private String mExtra; 1408 1409 HitTestResult() { 1410 mType = UNKNOWN_TYPE; 1411 } 1412 1413 private void setType(int type) { 1414 mType = type; 1415 } 1416 1417 private void setExtra(String extra) { 1418 mExtra = extra; 1419 } 1420 1421 /** 1422 * Gets the type of the hit test result. 1423 * @return See the XXX_TYPE constants defined in this class. 1424 */ 1425 public int getType() { 1426 return mType; 1427 } 1428 1429 /** 1430 * Gets additional type-dependant information about the result, see 1431 * {@link WebView#getHitTestResult()} for details. 1432 * @return may either be null or contain extra information about this result. 1433 */ 1434 public String getExtra() { 1435 return mExtra; 1436 } 1437 } 1438 1439 /** 1440 * Refer to {@link WebView#requestFocusNodeHref(Message)} for more information 1441 */ 1442 static class FocusNodeHref { 1443 static final String TITLE = "title"; 1444 static final String URL = "url"; 1445 static final String SRC = "src"; 1446 } 1447 1448 /** 1449 * Construct a new WebView with a Context object. 1450 * @param context A Context object used to access application assets. 1451 */ 1452 public WebView(Context context) { 1453 this(context, null); 1454 } 1455 1456 /** 1457 * Construct a new WebView with layout parameters. 1458 * @param context A Context object used to access application assets. 1459 * @param attrs An AttributeSet passed to our parent. 1460 */ 1461 public WebView(Context context, AttributeSet attrs) { 1462 this(context, attrs, com.android.internal.R.attr.webViewStyle); 1463 } 1464 1465 /** 1466 * Construct a new WebView with layout parameters and a default style. 1467 * @param context A Context object used to access application assets. 1468 * @param attrs An AttributeSet passed to our parent. 1469 * @param defStyle The default style resource ID. 1470 */ 1471 public WebView(Context context, AttributeSet attrs, int defStyle) { 1472 this(context, attrs, defStyle, false); 1473 } 1474 1475 /** 1476 * Construct a new WebView with layout parameters and a default style. 1477 * @param context A Context object used to access application assets. 1478 * @param attrs An AttributeSet passed to our parent. 1479 * @param defStyle The default style resource ID. 1480 * @param privateBrowsing If true the web view will be initialized in private mode. 1481 */ 1482 public WebView(Context context, AttributeSet attrs, int defStyle, 1483 boolean privateBrowsing) { 1484 this(context, attrs, defStyle, null, privateBrowsing); 1485 } 1486 1487 /** 1488 * Construct a new WebView with layout parameters, a default style and a set 1489 * of custom Javscript interfaces to be added to the WebView at initialization 1490 * time. This guarantees that these interfaces will be available when the JS 1491 * context is initialized. 1492 * @param context A Context object used to access application assets. 1493 * @param attrs An AttributeSet passed to our parent. 1494 * @param defStyle The default style resource ID. 1495 * @param javaScriptInterfaces is a Map of interface names, as keys, and 1496 * object implementing those interfaces, as values. 1497 * @param privateBrowsing If true the web view will be initialized in private mode. 1498 * @hide This is an implementation detail. 1499 */ 1500 protected WebView(Context context, AttributeSet attrs, int defStyle, 1501 Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) { 1502 super(context, attrs, defStyle); 1503 checkThread(); 1504 1505 if (context == null) { 1506 throw new IllegalArgumentException("Invalid context argument"); 1507 } 1508 1509 // Used by the chrome stack to find application paths 1510 JniUtil.setContext(context); 1511 1512 mCallbackProxy = new CallbackProxy(context, this); 1513 mViewManager = new ViewManager(this); 1514 L10nUtils.setApplicationContext(context.getApplicationContext()); 1515 mWebViewCore = new WebViewCore(context, this, mCallbackProxy, javaScriptInterfaces); 1516 mDatabase = WebViewDatabase.getInstance(context); 1517 mScroller = new OverScroller(context, null, 0, 0, false); //TODO Use OverScroller's flywheel 1518 mZoomManager = new ZoomManager(this, mCallbackProxy); 1519 1520 /* The init method must follow the creation of certain member variables, 1521 * such as the mZoomManager. 1522 */ 1523 init(); 1524 setupPackageListener(context); 1525 setupProxyListener(context); 1526 setupTrustStorageListener(context); 1527 updateMultiTouchSupport(context); 1528 1529 if (privateBrowsing) { 1530 startPrivateBrowsing(); 1531 } 1532 1533 mAutoFillData = new WebViewCore.AutoFillData(); 1534 } 1535 1536 private static class TrustStorageListener extends BroadcastReceiver { 1537 @Override 1538 public void onReceive(Context context, Intent intent) { 1539 if (intent.getAction().equals(KeyChain.ACTION_STORAGE_CHANGED)) { 1540 handleCertTrustChanged(); 1541 } 1542 } 1543 } 1544 private static TrustStorageListener sTrustStorageListener; 1545 1546 /** 1547 * Handles update to the trust storage. 1548 */ 1549 private static void handleCertTrustChanged() { 1550 // send a message for indicating trust storage change 1551 WebViewCore.sendStaticMessage(EventHub.TRUST_STORAGE_UPDATED, null); 1552 } 1553 1554 /* 1555 * @param context This method expects this to be a valid context. 1556 */ 1557 private static void setupTrustStorageListener(Context context) { 1558 if (sTrustStorageListener != null ) { 1559 return; 1560 } 1561 IntentFilter filter = new IntentFilter(); 1562 filter.addAction(KeyChain.ACTION_STORAGE_CHANGED); 1563 sTrustStorageListener = new TrustStorageListener(); 1564 Intent current = 1565 context.getApplicationContext().registerReceiver(sTrustStorageListener, filter); 1566 if (current != null) { 1567 handleCertTrustChanged(); 1568 } 1569 } 1570 1571 private static class ProxyReceiver extends BroadcastReceiver { 1572 @Override 1573 public void onReceive(Context context, Intent intent) { 1574 if (intent.getAction().equals(Proxy.PROXY_CHANGE_ACTION)) { 1575 handleProxyBroadcast(intent); 1576 } 1577 } 1578 } 1579 1580 /* 1581 * Receiver for PROXY_CHANGE_ACTION, will be null when it is not added handling broadcasts. 1582 */ 1583 private static ProxyReceiver sProxyReceiver; 1584 1585 /* 1586 * @param context This method expects this to be a valid context 1587 */ 1588 private static synchronized void setupProxyListener(Context context) { 1589 if (sProxyReceiver != null || sNotificationsEnabled == false) { 1590 return; 1591 } 1592 IntentFilter filter = new IntentFilter(); 1593 filter.addAction(Proxy.PROXY_CHANGE_ACTION); 1594 sProxyReceiver = new ProxyReceiver(); 1595 Intent currentProxy = context.getApplicationContext().registerReceiver( 1596 sProxyReceiver, filter); 1597 if (currentProxy != null) { 1598 handleProxyBroadcast(currentProxy); 1599 } 1600 } 1601 1602 /* 1603 * @param context This method expects this to be a valid context 1604 */ 1605 private static synchronized void disableProxyListener(Context context) { 1606 if (sProxyReceiver == null) 1607 return; 1608 1609 context.getApplicationContext().unregisterReceiver(sProxyReceiver); 1610 sProxyReceiver = null; 1611 } 1612 1613 private static void handleProxyBroadcast(Intent intent) { 1614 ProxyProperties proxyProperties = (ProxyProperties)intent.getExtra(Proxy.EXTRA_PROXY_INFO); 1615 if (proxyProperties == null || proxyProperties.getHost() == null) { 1616 WebViewCore.sendStaticMessage(EventHub.PROXY_CHANGED, null); 1617 return; 1618 } 1619 WebViewCore.sendStaticMessage(EventHub.PROXY_CHANGED, proxyProperties); 1620 } 1621 1622 /* 1623 * A variable to track if there is a receiver added for ACTION_PACKAGE_ADDED 1624 * or ACTION_PACKAGE_REMOVED. 1625 */ 1626 private static boolean sPackageInstallationReceiverAdded = false; 1627 1628 /* 1629 * A set of Google packages we monitor for the 1630 * navigator.isApplicationInstalled() API. Add additional packages as 1631 * needed. 1632 */ 1633 private static Set<String> sGoogleApps; 1634 static { 1635 sGoogleApps = new HashSet<String>(); 1636 sGoogleApps.add("com.google.android.youtube"); 1637 } 1638 1639 private static class PackageListener extends BroadcastReceiver { 1640 @Override 1641 public void onReceive(Context context, Intent intent) { 1642 final String action = intent.getAction(); 1643 final String packageName = intent.getData().getSchemeSpecificPart(); 1644 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); 1645 if (Intent.ACTION_PACKAGE_REMOVED.equals(action) && replacing) { 1646 // if it is replacing, refreshPlugins() when adding 1647 return; 1648 } 1649 1650 if (sGoogleApps.contains(packageName)) { 1651 if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { 1652 WebViewCore.sendStaticMessage(EventHub.ADD_PACKAGE_NAME, packageName); 1653 } else { 1654 WebViewCore.sendStaticMessage(EventHub.REMOVE_PACKAGE_NAME, packageName); 1655 } 1656 } 1657 1658 PluginManager pm = PluginManager.getInstance(context); 1659 if (pm.containsPluginPermissionAndSignatures(packageName)) { 1660 pm.refreshPlugins(Intent.ACTION_PACKAGE_ADDED.equals(action)); 1661 } 1662 } 1663 } 1664 1665 private void setupPackageListener(Context context) { 1666 1667 /* 1668 * we must synchronize the instance check and the creation of the 1669 * receiver to ensure that only ONE receiver exists for all WebView 1670 * instances. 1671 */ 1672 synchronized (WebView.class) { 1673 1674 // if the receiver already exists then we do not need to register it 1675 // again 1676 if (sPackageInstallationReceiverAdded) { 1677 return; 1678 } 1679 1680 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); 1681 filter.addAction(Intent.ACTION_PACKAGE_REMOVED); 1682 filter.addDataScheme("package"); 1683 BroadcastReceiver packageListener = new PackageListener(); 1684 context.getApplicationContext().registerReceiver(packageListener, filter); 1685 sPackageInstallationReceiverAdded = true; 1686 } 1687 1688 // check if any of the monitored apps are already installed 1689 AsyncTask<Void, Void, Set<String>> task = new AsyncTask<Void, Void, Set<String>>() { 1690 1691 @Override 1692 protected Set<String> doInBackground(Void... unused) { 1693 Set<String> installedPackages = new HashSet<String>(); 1694 PackageManager pm = mContext.getPackageManager(); 1695 for (String name : sGoogleApps) { 1696 try { 1697 pm.getPackageInfo(name, 1698 PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES); 1699 installedPackages.add(name); 1700 } catch (PackageManager.NameNotFoundException e) { 1701 // package not found 1702 } 1703 } 1704 return installedPackages; 1705 } 1706 1707 // Executes on the UI thread 1708 @Override 1709 protected void onPostExecute(Set<String> installedPackages) { 1710 if (mWebViewCore != null) { 1711 mWebViewCore.sendMessage(EventHub.ADD_PACKAGE_NAMES, installedPackages); 1712 } 1713 } 1714 }; 1715 task.execute(); 1716 } 1717 1718 void updateMultiTouchSupport(Context context) { 1719 mZoomManager.updateMultiTouchSupport(context); 1720 } 1721 1722 private void init() { 1723 OnTrimMemoryListener.init(getContext()); 1724 sDisableNavcache = nativeDisableNavcache(); 1725 setWillNotDraw(false); 1726 setFocusable(true); 1727 setFocusableInTouchMode(true); 1728 setClickable(true); 1729 setLongClickable(true); 1730 1731 final ViewConfiguration configuration = ViewConfiguration.get(getContext()); 1732 int slop = configuration.getScaledTouchSlop(); 1733 mTouchSlopSquare = slop * slop; 1734 slop = configuration.getScaledDoubleTapSlop(); 1735 mDoubleTapSlopSquare = slop * slop; 1736 final float density = getContext().getResources().getDisplayMetrics().density; 1737 // use one line height, 16 based on our current default font, for how 1738 // far we allow a touch be away from the edge of a link 1739 mNavSlop = (int) (16 * density); 1740 mZoomManager.init(density); 1741 mMaximumFling = configuration.getScaledMaximumFlingVelocity(); 1742 1743 // Compute the inverse of the density squared. 1744 DRAG_LAYER_INVERSE_DENSITY_SQUARED = 1 / (density * density); 1745 1746 mOverscrollDistance = configuration.getScaledOverscrollDistance(); 1747 mOverflingDistance = configuration.getScaledOverflingDistance(); 1748 1749 setScrollBarStyle(super.getScrollBarStyle()); 1750 // Initially use a size of two, since the user is likely to only hold 1751 // down two keys at a time (shift + another key) 1752 mKeysPressed = new Vector<Integer>(2); 1753 mHTML5VideoViewProxy = null ; 1754 } 1755 1756 @Override 1757 public boolean shouldDelayChildPressedState() { 1758 return true; 1759 } 1760 1761 /** 1762 * Adds accessibility APIs to JavaScript. 1763 * 1764 * Note: This method is responsible to performing the necessary 1765 * check if the accessibility APIs should be exposed. 1766 */ 1767 private void addAccessibilityApisToJavaScript() { 1768 if (AccessibilityManager.getInstance(mContext).isEnabled() 1769 && getSettings().getJavaScriptEnabled()) { 1770 // exposing the TTS for now ... 1771 final Context ctx = getContext(); 1772 if (ctx != null) { 1773 final String packageName = ctx.getPackageName(); 1774 if (packageName != null) { 1775 mTextToSpeech = new TextToSpeech(getContext(), null, null, 1776 packageName + ".**webview**", true); 1777 addJavascriptInterface(mTextToSpeech, ALIAS_ACCESSIBILITY_JS_INTERFACE); 1778 } 1779 } 1780 } 1781 } 1782 1783 /** 1784 * Removes accessibility APIs from JavaScript. 1785 */ 1786 private void removeAccessibilityApisFromJavaScript() { 1787 // exposing the TTS for now ... 1788 if (mTextToSpeech != null) { 1789 removeJavascriptInterface(ALIAS_ACCESSIBILITY_JS_INTERFACE); 1790 mTextToSpeech.shutdown(); 1791 mTextToSpeech = null; 1792 } 1793 } 1794 1795 @Override 1796 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 1797 super.onInitializeAccessibilityNodeInfo(info); 1798 info.setScrollable(isScrollableForAccessibility()); 1799 } 1800 1801 @Override 1802 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 1803 super.onInitializeAccessibilityEvent(event); 1804 event.setScrollable(isScrollableForAccessibility()); 1805 event.setScrollX(mScrollX); 1806 event.setScrollY(mScrollY); 1807 final int convertedContentWidth = contentToViewX(getContentWidth()); 1808 final int adjustedViewWidth = getWidth() - mPaddingLeft - mPaddingRight; 1809 event.setMaxScrollX(Math.max(convertedContentWidth - adjustedViewWidth, 0)); 1810 final int convertedContentHeight = contentToViewY(getContentHeight()); 1811 final int adjustedViewHeight = getHeight() - mPaddingTop - mPaddingBottom; 1812 event.setMaxScrollY(Math.max(convertedContentHeight - adjustedViewHeight, 0)); 1813 } 1814 1815 private boolean isScrollableForAccessibility() { 1816 return (contentToViewX(getContentWidth()) > getWidth() - mPaddingLeft - mPaddingRight 1817 || contentToViewY(getContentHeight()) > getHeight() - mPaddingTop - mPaddingBottom); 1818 } 1819 1820 @Override 1821 public void setOverScrollMode(int mode) { 1822 super.setOverScrollMode(mode); 1823 if (mode != OVER_SCROLL_NEVER) { 1824 if (mOverScrollGlow == null) { 1825 mOverScrollGlow = new OverScrollGlow(this); 1826 } 1827 } else { 1828 mOverScrollGlow = null; 1829 } 1830 } 1831 1832 /* package */ void adjustDefaultZoomDensity(int zoomDensity) { 1833 final float density = mContext.getResources().getDisplayMetrics().density 1834 * 100 / zoomDensity; 1835 updateDefaultZoomDensity(density); 1836 } 1837 1838 /* package */ void updateDefaultZoomDensity(float density) { 1839 mNavSlop = (int) (16 * density); 1840 mZoomManager.updateDefaultZoomDensity(density); 1841 } 1842 1843 /* package */ boolean onSavePassword(String schemePlusHost, String username, 1844 String password, final Message resumeMsg) { 1845 boolean rVal = false; 1846 if (resumeMsg == null) { 1847 // null resumeMsg implies saving password silently 1848 mDatabase.setUsernamePassword(schemePlusHost, username, password); 1849 } else { 1850 final Message remember = mPrivateHandler.obtainMessage( 1851 REMEMBER_PASSWORD); 1852 remember.getData().putString("host", schemePlusHost); 1853 remember.getData().putString("username", username); 1854 remember.getData().putString("password", password); 1855 remember.obj = resumeMsg; 1856 1857 final Message neverRemember = mPrivateHandler.obtainMessage( 1858 NEVER_REMEMBER_PASSWORD); 1859 neverRemember.getData().putString("host", schemePlusHost); 1860 neverRemember.getData().putString("username", username); 1861 neverRemember.getData().putString("password", password); 1862 neverRemember.obj = resumeMsg; 1863 1864 new AlertDialog.Builder(getContext()) 1865 .setTitle(com.android.internal.R.string.save_password_label) 1866 .setMessage(com.android.internal.R.string.save_password_message) 1867 .setPositiveButton(com.android.internal.R.string.save_password_notnow, 1868 new DialogInterface.OnClickListener() { 1869 @Override 1870 public void onClick(DialogInterface dialog, int which) { 1871 resumeMsg.sendToTarget(); 1872 } 1873 }) 1874 .setNeutralButton(com.android.internal.R.string.save_password_remember, 1875 new DialogInterface.OnClickListener() { 1876 @Override 1877 public void onClick(DialogInterface dialog, int which) { 1878 remember.sendToTarget(); 1879 } 1880 }) 1881 .setNegativeButton(com.android.internal.R.string.save_password_never, 1882 new DialogInterface.OnClickListener() { 1883 @Override 1884 public void onClick(DialogInterface dialog, int which) { 1885 neverRemember.sendToTarget(); 1886 } 1887 }) 1888 .setOnCancelListener(new OnCancelListener() { 1889 @Override 1890 public void onCancel(DialogInterface dialog) { 1891 resumeMsg.sendToTarget(); 1892 } 1893 }).show(); 1894 // Return true so that WebViewCore will pause while the dialog is 1895 // up. 1896 rVal = true; 1897 } 1898 return rVal; 1899 } 1900 1901 @Override 1902 public void setScrollBarStyle(int style) { 1903 if (style == View.SCROLLBARS_INSIDE_INSET 1904 || style == View.SCROLLBARS_OUTSIDE_INSET) { 1905 mOverlayHorizontalScrollbar = mOverlayVerticalScrollbar = false; 1906 } else { 1907 mOverlayHorizontalScrollbar = mOverlayVerticalScrollbar = true; 1908 } 1909 super.setScrollBarStyle(style); 1910 } 1911 1912 /** 1913 * Specify whether the horizontal scrollbar has overlay style. 1914 * @param overlay TRUE if horizontal scrollbar should have overlay style. 1915 */ 1916 public void setHorizontalScrollbarOverlay(boolean overlay) { 1917 checkThread(); 1918 mOverlayHorizontalScrollbar = overlay; 1919 } 1920 1921 /** 1922 * Specify whether the vertical scrollbar has overlay style. 1923 * @param overlay TRUE if vertical scrollbar should have overlay style. 1924 */ 1925 public void setVerticalScrollbarOverlay(boolean overlay) { 1926 checkThread(); 1927 mOverlayVerticalScrollbar = overlay; 1928 } 1929 1930 /** 1931 * Return whether horizontal scrollbar has overlay style 1932 * @return TRUE if horizontal scrollbar has overlay style. 1933 */ 1934 public boolean overlayHorizontalScrollbar() { 1935 checkThread(); 1936 return mOverlayHorizontalScrollbar; 1937 } 1938 1939 /** 1940 * Return whether vertical scrollbar has overlay style 1941 * @return TRUE if vertical scrollbar has overlay style. 1942 */ 1943 public boolean overlayVerticalScrollbar() { 1944 checkThread(); 1945 return mOverlayVerticalScrollbar; 1946 } 1947 1948 /* 1949 * Return the width of the view where the content of WebView should render 1950 * to. 1951 * Note: this can be called from WebCoreThread. 1952 */ 1953 /* package */ int getViewWidth() { 1954 if (!isVerticalScrollBarEnabled() || mOverlayVerticalScrollbar) { 1955 return getWidth(); 1956 } else { 1957 return Math.max(0, getWidth() - getVerticalScrollbarWidth()); 1958 } 1959 } 1960 1961 /** 1962 * Returns the height (in pixels) of the embedded title bar (if any). Does not care about 1963 * scrolling 1964 * @hide 1965 */ 1966 protected int getTitleHeight() { 1967 return mTitleBar != null ? mTitleBar.getHeight() : 0; 1968 } 1969 1970 /** 1971 * Return the visible height (in pixels) of the embedded title bar (if any). 1972 * 1973 * @return This method is obsolete and always returns 0. 1974 * @deprecated This method is now obsolete. 1975 */ 1976 @Deprecated 1977 public int getVisibleTitleHeight() { 1978 // Actually, this method returns the height of the embedded title bar if one is set via the 1979 // hidden setEmbeddedTitleBar method. 1980 checkThread(); 1981 return getVisibleTitleHeightImpl(); 1982 } 1983 1984 private int getVisibleTitleHeightImpl() { 1985 // need to restrict mScrollY due to over scroll 1986 return Math.max(getTitleHeight() - Math.max(0, mScrollY), 1987 getOverlappingActionModeHeight()); 1988 } 1989 1990 private int mCachedOverlappingActionModeHeight = -1; 1991 1992 private int getOverlappingActionModeHeight() { 1993 if (mFindCallback == null) { 1994 return 0; 1995 } 1996 if (mCachedOverlappingActionModeHeight < 0) { 1997 getGlobalVisibleRect(mGlobalVisibleRect, mGlobalVisibleOffset); 1998 mCachedOverlappingActionModeHeight = Math.max(0, 1999 mFindCallback.getActionModeGlobalBottom() - mGlobalVisibleRect.top); 2000 } 2001 return mCachedOverlappingActionModeHeight; 2002 } 2003 2004 /* 2005 * Return the height of the view where the content of WebView should render 2006 * to. Note that this excludes mTitleBar, if there is one. 2007 * Note: this can be called from WebCoreThread. 2008 */ 2009 /* package */ int getViewHeight() { 2010 return getViewHeightWithTitle() - getVisibleTitleHeightImpl(); 2011 } 2012 2013 int getViewHeightWithTitle() { 2014 int height = getHeight(); 2015 if (isHorizontalScrollBarEnabled() && !mOverlayHorizontalScrollbar) { 2016 height -= getHorizontalScrollbarHeight(); 2017 } 2018 return height; 2019 } 2020 2021 /** 2022 * @return The SSL certificate for the main top-level page or null if 2023 * there is no certificate (the site is not secure). 2024 */ 2025 public SslCertificate getCertificate() { 2026 checkThread(); 2027 return mCertificate; 2028 } 2029 2030 /** 2031 * Sets the SSL certificate for the main top-level page. 2032 */ 2033 public void setCertificate(SslCertificate certificate) { 2034 checkThread(); 2035 if (DebugFlags.WEB_VIEW) { 2036 Log.v(LOGTAG, "setCertificate=" + certificate); 2037 } 2038 // here, the certificate can be null (if the site is not secure) 2039 mCertificate = certificate; 2040 } 2041 2042 //------------------------------------------------------------------------- 2043 // Methods called by activity 2044 //------------------------------------------------------------------------- 2045 2046 /** 2047 * Save the username and password for a particular host in the WebView's 2048 * internal database. 2049 * @param host The host that required the credentials. 2050 * @param username The username for the given host. 2051 * @param password The password for the given host. 2052 */ 2053 public void savePassword(String host, String username, String password) { 2054 checkThread(); 2055 mDatabase.setUsernamePassword(host, username, password); 2056 } 2057 2058 /** 2059 * Set the HTTP authentication credentials for a given host and realm. 2060 * 2061 * @param host The host for the credentials. 2062 * @param realm The realm for the credentials. 2063 * @param username The username for the password. If it is null, it means 2064 * password can't be saved. 2065 * @param password The password 2066 */ 2067 public void setHttpAuthUsernamePassword(String host, String realm, 2068 String username, String password) { 2069 checkThread(); 2070 mDatabase.setHttpAuthUsernamePassword(host, realm, username, password); 2071 } 2072 2073 /** 2074 * Retrieve the HTTP authentication username and password for a given 2075 * host & realm pair 2076 * 2077 * @param host The host for which the credentials apply. 2078 * @param realm The realm for which the credentials apply. 2079 * @return String[] if found, String[0] is username, which can be null and 2080 * String[1] is password. Return null if it can't find anything. 2081 */ 2082 public String[] getHttpAuthUsernamePassword(String host, String realm) { 2083 checkThread(); 2084 return mDatabase.getHttpAuthUsernamePassword(host, realm); 2085 } 2086 2087 /** 2088 * Remove Find or Select ActionModes, if active. 2089 */ 2090 private void clearActionModes() { 2091 if (mSelectCallback != null) { 2092 mSelectCallback.finish(); 2093 } 2094 if (mFindCallback != null) { 2095 mFindCallback.finish(); 2096 } 2097 } 2098 2099 /** 2100 * Called to clear state when moving from one page to another, or changing 2101 * in some other way that makes elements associated with the current page 2102 * (such as WebTextView or ActionModes) no longer relevant. 2103 */ 2104 private void clearHelpers() { 2105 clearTextEntry(); 2106 clearActionModes(); 2107 dismissFullScreenMode(); 2108 cancelSelectDialog(); 2109 } 2110 2111 private void cancelSelectDialog() { 2112 if (mListBoxDialog != null) { 2113 mListBoxDialog.cancel(); 2114 mListBoxDialog = null; 2115 } 2116 } 2117 2118 /** 2119 * Destroy the internal state of the WebView. This method should be called 2120 * after the WebView has been removed from the view system. No other 2121 * methods may be called on a WebView after destroy. 2122 */ 2123 public void destroy() { 2124 checkThread(); 2125 destroyImpl(); 2126 } 2127 2128 private void destroyImpl() { 2129 clearHelpers(); 2130 if (mListBoxDialog != null) { 2131 mListBoxDialog.dismiss(); 2132 mListBoxDialog = null; 2133 } 2134 // remove so that it doesn't cause events 2135 if (mWebTextView != null) { 2136 mWebTextView.remove(); 2137 mWebTextView = null; 2138 } 2139 if (mNativeClass != 0) nativeStopGL(); 2140 if (mWebViewCore != null) { 2141 // Set the handlers to null before destroying WebViewCore so no 2142 // more messages will be posted. 2143 mCallbackProxy.setWebViewClient(null); 2144 mCallbackProxy.setWebChromeClient(null); 2145 // Tell WebViewCore to destroy itself 2146 synchronized (this) { 2147 WebViewCore webViewCore = mWebViewCore; 2148 mWebViewCore = null; // prevent using partial webViewCore 2149 webViewCore.destroy(); 2150 } 2151 // Remove any pending messages that might not be serviced yet. 2152 mPrivateHandler.removeCallbacksAndMessages(null); 2153 mCallbackProxy.removeCallbacksAndMessages(null); 2154 // Wake up the WebCore thread just in case it is waiting for a 2155 // JavaScript dialog. 2156 synchronized (mCallbackProxy) { 2157 mCallbackProxy.notify(); 2158 } 2159 } 2160 if (mNativeClass != 0) { 2161 nativeDestroy(); 2162 mNativeClass = 0; 2163 } 2164 } 2165 2166 /** 2167 * Enables platform notifications of data state and proxy changes. 2168 * Notifications are enabled by default. 2169 * 2170 * @deprecated This method is now obsolete. 2171 */ 2172 @Deprecated 2173 public static void enablePlatformNotifications() { 2174 checkThread(); 2175 synchronized (WebView.class) { 2176 sNotificationsEnabled = true; 2177 Context context = JniUtil.getContext(); 2178 if (context != null) 2179 setupProxyListener(context); 2180 } 2181 } 2182 2183 /** 2184 * Disables platform notifications of data state and proxy changes. 2185 * Notifications are enabled by default. 2186 * 2187 * @deprecated This method is now obsolete. 2188 */ 2189 @Deprecated 2190 public static void disablePlatformNotifications() { 2191 checkThread(); 2192 synchronized (WebView.class) { 2193 sNotificationsEnabled = false; 2194 Context context = JniUtil.getContext(); 2195 if (context != null) 2196 disableProxyListener(context); 2197 } 2198 } 2199 2200 /** 2201 * Sets JavaScript engine flags. 2202 * 2203 * @param flags JS engine flags in a String 2204 * 2205 * @hide This is an implementation detail. 2206 */ 2207 public void setJsFlags(String flags) { 2208 checkThread(); 2209 mWebViewCore.sendMessage(EventHub.SET_JS_FLAGS, flags); 2210 } 2211 2212 /** 2213 * Inform WebView of the network state. This is used to set 2214 * the JavaScript property window.navigator.isOnline and 2215 * generates the online/offline event as specified in HTML5, sec. 5.7.7 2216 * @param networkUp boolean indicating if network is available 2217 */ 2218 public void setNetworkAvailable(boolean networkUp) { 2219 checkThread(); 2220 mWebViewCore.sendMessage(EventHub.SET_NETWORK_STATE, 2221 networkUp ? 1 : 0, 0); 2222 } 2223 2224 /** 2225 * Inform WebView about the current network type. 2226 * {@hide} 2227 */ 2228 public void setNetworkType(String type, String subtype) { 2229 checkThread(); 2230 Map<String, String> map = new HashMap<String, String>(); 2231 map.put("type", type); 2232 map.put("subtype", subtype); 2233 mWebViewCore.sendMessage(EventHub.SET_NETWORK_TYPE, map); 2234 } 2235 /** 2236 * Save the state of this WebView used in 2237 * {@link android.app.Activity#onSaveInstanceState}. Please note that this 2238 * method no longer stores the display data for this WebView. The previous 2239 * behavior could potentially leak files if {@link #restoreState} was never 2240 * called. See {@link #savePicture} and {@link #restorePicture} for saving 2241 * and restoring the display data. 2242 * @param outState The Bundle to store the WebView state. 2243 * @return The same copy of the back/forward list used to save the state. If 2244 * saveState fails, the returned list will be null. 2245 * @see #savePicture 2246 * @see #restorePicture 2247 */ 2248 public WebBackForwardList saveState(Bundle outState) { 2249 checkThread(); 2250 if (outState == null) { 2251 return null; 2252 } 2253 // We grab a copy of the back/forward list because a client of WebView 2254 // may have invalidated the history list by calling clearHistory. 2255 WebBackForwardList list = copyBackForwardList(); 2256 final int currentIndex = list.getCurrentIndex(); 2257 final int size = list.getSize(); 2258 // We should fail saving the state if the list is empty or the index is 2259 // not in a valid range. 2260 if (currentIndex < 0 || currentIndex >= size || size == 0) { 2261 return null; 2262 } 2263 outState.putInt("index", currentIndex); 2264 // FIXME: This should just be a byte[][] instead of ArrayList but 2265 // Parcel.java does not have the code to handle multi-dimensional 2266 // arrays. 2267 ArrayList<byte[]> history = new ArrayList<byte[]>(size); 2268 for (int i = 0; i < size; i++) { 2269 WebHistoryItem item = list.getItemAtIndex(i); 2270 if (null == item) { 2271 // FIXME: this shouldn't happen 2272 // need to determine how item got set to null 2273 Log.w(LOGTAG, "saveState: Unexpected null history item."); 2274 return null; 2275 } 2276 byte[] data = item.getFlattenedData(); 2277 if (data == null) { 2278 // It would be very odd to not have any data for a given history 2279 // item. And we will fail to rebuild the history list without 2280 // flattened data. 2281 return null; 2282 } 2283 history.add(data); 2284 } 2285 outState.putSerializable("history", history); 2286 if (mCertificate != null) { 2287 outState.putBundle("certificate", 2288 SslCertificate.saveState(mCertificate)); 2289 } 2290 outState.putBoolean("privateBrowsingEnabled", isPrivateBrowsingEnabled()); 2291 mZoomManager.saveZoomState(outState); 2292 return list; 2293 } 2294 2295 /** 2296 * Save the current display data to the Bundle given. Used in conjunction 2297 * with {@link #saveState}. 2298 * @param b A Bundle to store the display data. 2299 * @param dest The file to store the serialized picture data. Will be 2300 * overwritten with this WebView's picture data. 2301 * @return True if the picture was successfully saved. 2302 * @deprecated This method is now obsolete. 2303 */ 2304 @Deprecated 2305 public boolean savePicture(Bundle b, final File dest) { 2306 checkThread(); 2307 if (dest == null || b == null) { 2308 return false; 2309 } 2310 final Picture p = capturePicture(); 2311 // Use a temporary file while writing to ensure the destination file 2312 // contains valid data. 2313 final File temp = new File(dest.getPath() + ".writing"); 2314 new Thread(new Runnable() { 2315 @Override 2316 public void run() { 2317 FileOutputStream out = null; 2318 try { 2319 out = new FileOutputStream(temp); 2320 p.writeToStream(out); 2321 // Writing the picture succeeded, rename the temporary file 2322 // to the destination. 2323 temp.renameTo(dest); 2324 } catch (Exception e) { 2325 // too late to do anything about it. 2326 } finally { 2327 if (out != null) { 2328 try { 2329 out.close(); 2330 } catch (Exception e) { 2331 // Can't do anything about that 2332 } 2333 } 2334 temp.delete(); 2335 } 2336 } 2337 }).start(); 2338 // now update the bundle 2339 b.putInt("scrollX", mScrollX); 2340 b.putInt("scrollY", mScrollY); 2341 mZoomManager.saveZoomState(b); 2342 return true; 2343 } 2344 2345 private void restoreHistoryPictureFields(Picture p, Bundle b) { 2346 int sx = b.getInt("scrollX", 0); 2347 int sy = b.getInt("scrollY", 0); 2348 2349 mDrawHistory = true; 2350 mHistoryPicture = p; 2351 2352 mScrollX = sx; 2353 mScrollY = sy; 2354 mZoomManager.restoreZoomState(b); 2355 final float scale = mZoomManager.getScale(); 2356 mHistoryWidth = Math.round(p.getWidth() * scale); 2357 mHistoryHeight = Math.round(p.getHeight() * scale); 2358 2359 invalidate(); 2360 } 2361 2362 /** 2363 * Restore the display data that was save in {@link #savePicture}. Used in 2364 * conjunction with {@link #restoreState}. 2365 * 2366 * Note that this will not work if the WebView is hardware accelerated. 2367 * @param b A Bundle containing the saved display data. 2368 * @param src The file where the picture data was stored. 2369 * @return True if the picture was successfully restored. 2370 * @deprecated This method is now obsolete. 2371 */ 2372 @Deprecated 2373 public boolean restorePicture(Bundle b, File src) { 2374 checkThread(); 2375 if (src == null || b == null) { 2376 return false; 2377 } 2378 if (!src.exists()) { 2379 return false; 2380 } 2381 try { 2382 final FileInputStream in = new FileInputStream(src); 2383 final Bundle copy = new Bundle(b); 2384 new Thread(new Runnable() { 2385 @Override 2386 public void run() { 2387 try { 2388 final Picture p = Picture.createFromStream(in); 2389 if (p != null) { 2390 // Post a runnable on the main thread to update the 2391 // history picture fields. 2392 mPrivateHandler.post(new Runnable() { 2393 @Override 2394 public void run() { 2395 restoreHistoryPictureFields(p, copy); 2396 } 2397 }); 2398 } 2399 } finally { 2400 try { 2401 in.close(); 2402 } catch (Exception e) { 2403 // Nothing we can do now. 2404 } 2405 } 2406 } 2407 }).start(); 2408 } catch (FileNotFoundException e){ 2409 e.printStackTrace(); 2410 } 2411 return true; 2412 } 2413 2414 /** 2415 * Saves the view data to the output stream. The output is highly 2416 * version specific, and may not be able to be loaded by newer versions 2417 * of WebView. 2418 * @param stream The {@link OutputStream} to save to 2419 * @return True if saved successfully 2420 * @hide 2421 */ 2422 public boolean saveViewState(OutputStream stream) { 2423 try { 2424 return ViewStateSerializer.serializeViewState(stream, this); 2425 } catch (IOException e) { 2426 Log.w(LOGTAG, "Failed to saveViewState", e); 2427 } 2428 return false; 2429 } 2430 2431 /** 2432 * Loads the view data from the input stream. See 2433 * {@link #saveViewState(OutputStream)} for more information. 2434 * @param stream The {@link InputStream} to load from 2435 * @return True if loaded successfully 2436 * @hide 2437 */ 2438 public boolean loadViewState(InputStream stream) { 2439 try { 2440 mLoadedPicture = ViewStateSerializer.deserializeViewState(stream, this); 2441 mBlockWebkitViewMessages = true; 2442 setNewPicture(mLoadedPicture, true); 2443 mLoadedPicture.mViewState = null; 2444 return true; 2445 } catch (IOException e) { 2446 Log.w(LOGTAG, "Failed to loadViewState", e); 2447 } 2448 return false; 2449 } 2450 2451 /** 2452 * Clears the view state set with {@link #loadViewState(InputStream)}. 2453 * This WebView will then switch to showing the content from webkit 2454 * @hide 2455 */ 2456 public void clearViewState() { 2457 mBlockWebkitViewMessages = false; 2458 mLoadedPicture = null; 2459 invalidate(); 2460 } 2461 2462 /** 2463 * Restore the state of this WebView from the given map used in 2464 * {@link android.app.Activity#onRestoreInstanceState}. This method should 2465 * be called to restore the state of the WebView before using the object. If 2466 * it is called after the WebView has had a chance to build state (load 2467 * pages, create a back/forward list, etc.) there may be undesirable 2468 * side-effects. Please note that this method no longer restores the 2469 * display data for this WebView. See {@link #savePicture} and {@link 2470 * #restorePicture} for saving and restoring the display data. 2471 * @param inState The incoming Bundle of state. 2472 * @return The restored back/forward list or null if restoreState failed. 2473 * @see #savePicture 2474 * @see #restorePicture 2475 */ 2476 public WebBackForwardList restoreState(Bundle inState) { 2477 checkThread(); 2478 WebBackForwardList returnList = null; 2479 if (inState == null) { 2480 return returnList; 2481 } 2482 if (inState.containsKey("index") && inState.containsKey("history")) { 2483 mCertificate = SslCertificate.restoreState( 2484 inState.getBundle("certificate")); 2485 2486 final WebBackForwardList list = mCallbackProxy.getBackForwardList(); 2487 final int index = inState.getInt("index"); 2488 // We can't use a clone of the list because we need to modify the 2489 // shared copy, so synchronize instead to prevent concurrent 2490 // modifications. 2491 synchronized (list) { 2492 final List<byte[]> history = 2493 (List<byte[]>) inState.getSerializable("history"); 2494 final int size = history.size(); 2495 // Check the index bounds so we don't crash in native code while 2496 // restoring the history index. 2497 if (index < 0 || index >= size) { 2498 return null; 2499 } 2500 for (int i = 0; i < size; i++) { 2501 byte[] data = history.remove(0); 2502 if (data == null) { 2503 // If we somehow have null data, we cannot reconstruct 2504 // the item and thus our history list cannot be rebuilt. 2505 return null; 2506 } 2507 WebHistoryItem item = new WebHistoryItem(data); 2508 list.addHistoryItem(item); 2509 } 2510 // Grab the most recent copy to return to the caller. 2511 returnList = copyBackForwardList(); 2512 // Update the copy to have the correct index. 2513 returnList.setCurrentIndex(index); 2514 } 2515 // Restore private browsing setting. 2516 if (inState.getBoolean("privateBrowsingEnabled")) { 2517 getSettings().setPrivateBrowsingEnabled(true); 2518 } 2519 mZoomManager.restoreZoomState(inState); 2520 // Remove all pending messages because we are restoring previous 2521 // state. 2522 mWebViewCore.removeMessages(); 2523 // Send a restore state message. 2524 mWebViewCore.sendMessage(EventHub.RESTORE_STATE, index); 2525 } 2526 return returnList; 2527 } 2528 2529 /** 2530 * Load the given URL with the specified additional HTTP headers. 2531 * @param url The URL of the resource to load. 2532 * @param additionalHttpHeaders The additional headers to be used in the 2533 * HTTP request for this URL, specified as a map from name to 2534 * value. Note that if this map contains any of the headers 2535 * that are set by default by the WebView, such as those 2536 * controlling caching, accept types or the User-Agent, their 2537 * values may be overriden by the WebView's defaults. 2538 */ 2539 public void loadUrl(String url, Map<String, String> additionalHttpHeaders) { 2540 checkThread(); 2541 loadUrlImpl(url, additionalHttpHeaders); 2542 } 2543 2544 private void loadUrlImpl(String url, Map<String, String> extraHeaders) { 2545 switchOutDrawHistory(); 2546 WebViewCore.GetUrlData arg = new WebViewCore.GetUrlData(); 2547 arg.mUrl = url; 2548 arg.mExtraHeaders = extraHeaders; 2549 mWebViewCore.sendMessage(EventHub.LOAD_URL, arg); 2550 clearHelpers(); 2551 } 2552 2553 /** 2554 * Load the given URL. 2555 * @param url The URL of the resource to load. 2556 */ 2557 public void loadUrl(String url) { 2558 checkThread(); 2559 loadUrlImpl(url); 2560 } 2561 2562 private void loadUrlImpl(String url) { 2563 if (url == null) { 2564 return; 2565 } 2566 loadUrlImpl(url, null); 2567 } 2568 2569 /** 2570 * Load the url with postData using "POST" method into the WebView. If url 2571 * is not a network url, it will be loaded with {link 2572 * {@link #loadUrl(String)} instead. 2573 * 2574 * @param url The url of the resource to load. 2575 * @param postData The data will be passed to "POST" request. 2576 */ 2577 public void postUrl(String url, byte[] postData) { 2578 checkThread(); 2579 if (URLUtil.isNetworkUrl(url)) { 2580 switchOutDrawHistory(); 2581 WebViewCore.PostUrlData arg = new WebViewCore.PostUrlData(); 2582 arg.mUrl = url; 2583 arg.mPostData = postData; 2584 mWebViewCore.sendMessage(EventHub.POST_URL, arg); 2585 clearHelpers(); 2586 } else { 2587 loadUrlImpl(url); 2588 } 2589 } 2590 2591 /** 2592 * Load the given data into the WebView using a 'data' scheme URL. 2593 * <p> 2594 * Note that JavaScript's same origin policy means that script running in a 2595 * page loaded using this method will be unable to access content loaded 2596 * using any scheme other than 'data', including 'http(s)'. To avoid this 2597 * restriction, use {@link 2598 * #loadDataWithBaseURL(String,String,String,String,String) 2599 * loadDataWithBaseURL()} with an appropriate base URL. 2600 * <p> 2601 * If the value of the encoding parameter is 'base64', then the data must 2602 * be encoded as base64. Otherwise, the data must use ASCII encoding for 2603 * octets inside the range of safe URL characters and use the standard %xx 2604 * hex encoding of URLs for octets outside that range. For example, 2605 * '#', '%', '\', '?' should be replaced by %23, %25, %27, %3f respectively. 2606 * <p> 2607 * The 'data' scheme URL formed by this method uses the default US-ASCII 2608 * charset. If you need need to set a different charset, you should form a 2609 * 'data' scheme URL which explicitly specifies a charset parameter in the 2610 * mediatype portion of the URL and call {@link #loadUrl(String)} instead. 2611 * Note that the charset obtained from the mediatype portion of a data URL 2612 * always overrides that specified in the HTML or XML document itself. 2613 * @param data A String of data in the given encoding. 2614 * @param mimeType The MIME type of the data, e.g. 'text/html'. 2615 * @param encoding The encoding of the data. 2616 */ 2617 public void loadData(String data, String mimeType, String encoding) { 2618 checkThread(); 2619 loadDataImpl(data, mimeType, encoding); 2620 } 2621 2622 private void loadDataImpl(String data, String mimeType, String encoding) { 2623 StringBuilder dataUrl = new StringBuilder("data:"); 2624 dataUrl.append(mimeType); 2625 if ("base64".equals(encoding)) { 2626 dataUrl.append(";base64"); 2627 } 2628 dataUrl.append(","); 2629 dataUrl.append(data); 2630 loadUrlImpl(dataUrl.toString()); 2631 } 2632 2633 /** 2634 * Load the given data into the WebView, using baseUrl as the base URL for 2635 * the content. The base URL is used both to resolve relative URLs and when 2636 * applying JavaScript's same origin policy. The historyUrl is used for the 2637 * history entry. 2638 * <p> 2639 * Note that content specified in this way can access local device files 2640 * (via 'file' scheme URLs) only if baseUrl specifies a scheme other than 2641 * 'http', 'https', 'ftp', 'ftps', 'about' or 'javascript'. 2642 * <p> 2643 * If the base URL uses the data scheme, this method is equivalent to 2644 * calling {@link #loadData(String,String,String) loadData()} and the 2645 * historyUrl is ignored. 2646 * @param baseUrl URL to use as the page's base URL. If null defaults to 2647 * 'about:blank' 2648 * @param data A String of data in the given encoding. 2649 * @param mimeType The MIMEType of the data, e.g. 'text/html'. If null, 2650 * defaults to 'text/html'. 2651 * @param encoding The encoding of the data. 2652 * @param historyUrl URL to use as the history entry, if null defaults to 2653 * 'about:blank'. 2654 */ 2655 public void loadDataWithBaseURL(String baseUrl, String data, 2656 String mimeType, String encoding, String historyUrl) { 2657 checkThread(); 2658 2659 if (baseUrl != null && baseUrl.toLowerCase().startsWith("data:")) { 2660 loadDataImpl(data, mimeType, encoding); 2661 return; 2662 } 2663 switchOutDrawHistory(); 2664 WebViewCore.BaseUrlData arg = new WebViewCore.BaseUrlData(); 2665 arg.mBaseUrl = baseUrl; 2666 arg.mData = data; 2667 arg.mMimeType = mimeType; 2668 arg.mEncoding = encoding; 2669 arg.mHistoryUrl = historyUrl; 2670 mWebViewCore.sendMessage(EventHub.LOAD_DATA, arg); 2671 clearHelpers(); 2672 } 2673 2674 /** 2675 * Saves the current view as a web archive. 2676 * 2677 * @param filename The filename where the archive should be placed. 2678 */ 2679 public void saveWebArchive(String filename) { 2680 checkThread(); 2681 saveWebArchiveImpl(filename, false, null); 2682 } 2683 2684 /* package */ static class SaveWebArchiveMessage { 2685 SaveWebArchiveMessage (String basename, boolean autoname, ValueCallback<String> callback) { 2686 mBasename = basename; 2687 mAutoname = autoname; 2688 mCallback = callback; 2689 } 2690 2691 /* package */ final String mBasename; 2692 /* package */ final boolean mAutoname; 2693 /* package */ final ValueCallback<String> mCallback; 2694 /* package */ String mResultFile; 2695 } 2696 2697 /** 2698 * Saves the current view as a web archive. 2699 * 2700 * @param basename The filename where the archive should be placed. 2701 * @param autoname If false, takes basename to be a file. If true, basename 2702 * is assumed to be a directory in which a filename will be 2703 * chosen according to the url of the current page. 2704 * @param callback Called after the web archive has been saved. The 2705 * parameter for onReceiveValue will either be the filename 2706 * under which the file was saved, or null if saving the 2707 * file failed. 2708 */ 2709 public void saveWebArchive(String basename, boolean autoname, ValueCallback<String> callback) { 2710 checkThread(); 2711 saveWebArchiveImpl(basename, autoname, callback); 2712 } 2713 2714 private void saveWebArchiveImpl(String basename, boolean autoname, 2715 ValueCallback<String> callback) { 2716 mWebViewCore.sendMessage(EventHub.SAVE_WEBARCHIVE, 2717 new SaveWebArchiveMessage(basename, autoname, callback)); 2718 } 2719 2720 /** 2721 * Stop the current load. 2722 */ 2723 public void stopLoading() { 2724 checkThread(); 2725 // TODO: should we clear all the messages in the queue before sending 2726 // STOP_LOADING? 2727 switchOutDrawHistory(); 2728 mWebViewCore.sendMessage(EventHub.STOP_LOADING); 2729 } 2730 2731 /** 2732 * Reload the current url. 2733 */ 2734 public void reload() { 2735 checkThread(); 2736 clearHelpers(); 2737 switchOutDrawHistory(); 2738 mWebViewCore.sendMessage(EventHub.RELOAD); 2739 } 2740 2741 /** 2742 * Return true if this WebView has a back history item. 2743 * @return True iff this WebView has a back history item. 2744 */ 2745 public boolean canGoBack() { 2746 checkThread(); 2747 WebBackForwardList l = mCallbackProxy.getBackForwardList(); 2748 synchronized (l) { 2749 if (l.getClearPending()) { 2750 return false; 2751 } else { 2752 return l.getCurrentIndex() > 0; 2753 } 2754 } 2755 } 2756 2757 /** 2758 * Go back in the history of this WebView. 2759 */ 2760 public void goBack() { 2761 checkThread(); 2762 goBackOrForwardImpl(-1); 2763 } 2764 2765 /** 2766 * Return true if this WebView has a forward history item. 2767 * @return True iff this Webview has a forward history item. 2768 */ 2769 public boolean canGoForward() { 2770 checkThread(); 2771 WebBackForwardList l = mCallbackProxy.getBackForwardList(); 2772 synchronized (l) { 2773 if (l.getClearPending()) { 2774 return false; 2775 } else { 2776 return l.getCurrentIndex() < l.getSize() - 1; 2777 } 2778 } 2779 } 2780 2781 /** 2782 * Go forward in the history of this WebView. 2783 */ 2784 public void goForward() { 2785 checkThread(); 2786 goBackOrForwardImpl(1); 2787 } 2788 2789 /** 2790 * Return true if the page can go back or forward the given 2791 * number of steps. 2792 * @param steps The negative or positive number of steps to move the 2793 * history. 2794 */ 2795 public boolean canGoBackOrForward(int steps) { 2796 checkThread(); 2797 WebBackForwardList l = mCallbackProxy.getBackForwardList(); 2798 synchronized (l) { 2799 if (l.getClearPending()) { 2800 return false; 2801 } else { 2802 int newIndex = l.getCurrentIndex() + steps; 2803 return newIndex >= 0 && newIndex < l.getSize(); 2804 } 2805 } 2806 } 2807 2808 /** 2809 * Go to the history item that is the number of steps away from 2810 * the current item. Steps is negative if backward and positive 2811 * if forward. 2812 * @param steps The number of steps to take back or forward in the back 2813 * forward list. 2814 */ 2815 public void goBackOrForward(int steps) { 2816 checkThread(); 2817 goBackOrForwardImpl(steps); 2818 } 2819 2820 private void goBackOrForwardImpl(int steps) { 2821 goBackOrForward(steps, false); 2822 } 2823 2824 private void goBackOrForward(int steps, boolean ignoreSnapshot) { 2825 if (steps != 0) { 2826 clearHelpers(); 2827 mWebViewCore.sendMessage(EventHub.GO_BACK_FORWARD, steps, 2828 ignoreSnapshot ? 1 : 0); 2829 } 2830 } 2831 2832 /** 2833 * Returns true if private browsing is enabled in this WebView. 2834 */ 2835 public boolean isPrivateBrowsingEnabled() { 2836 checkThread(); 2837 return getSettings().isPrivateBrowsingEnabled(); 2838 } 2839 2840 private void startPrivateBrowsing() { 2841 getSettings().setPrivateBrowsingEnabled(true); 2842 } 2843 2844 private boolean extendScroll(int y) { 2845 int finalY = mScroller.getFinalY(); 2846 int newY = pinLocY(finalY + y); 2847 if (newY == finalY) return false; 2848 mScroller.setFinalY(newY); 2849 mScroller.extendDuration(computeDuration(0, y)); 2850 return true; 2851 } 2852 2853 /** 2854 * Scroll the contents of the view up by half the view size 2855 * @param top true to jump to the top of the page 2856 * @return true if the page was scrolled 2857 */ 2858 public boolean pageUp(boolean top) { 2859 checkThread(); 2860 if (mNativeClass == 0) { 2861 return false; 2862 } 2863 nativeClearCursor(); // start next trackball movement from page edge 2864 if (top) { 2865 // go to the top of the document 2866 return pinScrollTo(mScrollX, 0, true, 0); 2867 } 2868 // Page up 2869 int h = getHeight(); 2870 int y; 2871 if (h > 2 * PAGE_SCROLL_OVERLAP) { 2872 y = -h + PAGE_SCROLL_OVERLAP; 2873 } else { 2874 y = -h / 2; 2875 } 2876 return mScroller.isFinished() ? pinScrollBy(0, y, true, 0) 2877 : extendScroll(y); 2878 } 2879 2880 /** 2881 * Scroll the contents of the view down by half the page size 2882 * @param bottom true to jump to bottom of page 2883 * @return true if the page was scrolled 2884 */ 2885 public boolean pageDown(boolean bottom) { 2886 checkThread(); 2887 if (mNativeClass == 0) { 2888 return false; 2889 } 2890 nativeClearCursor(); // start next trackball movement from page edge 2891 if (bottom) { 2892 return pinScrollTo(mScrollX, computeRealVerticalScrollRange(), true, 0); 2893 } 2894 // Page down. 2895 int h = getHeight(); 2896 int y; 2897 if (h > 2 * PAGE_SCROLL_OVERLAP) { 2898 y = h - PAGE_SCROLL_OVERLAP; 2899 } else { 2900 y = h / 2; 2901 } 2902 return mScroller.isFinished() ? pinScrollBy(0, y, true, 0) 2903 : extendScroll(y); 2904 } 2905 2906 /** 2907 * Clear the view so that onDraw() will draw nothing but white background, 2908 * and onMeasure() will return 0 if MeasureSpec is not MeasureSpec.EXACTLY 2909 */ 2910 public void clearView() { 2911 checkThread(); 2912 mContentWidth = 0; 2913 mContentHeight = 0; 2914 setBaseLayer(0, null, false, false); 2915 mWebViewCore.sendMessage(EventHub.CLEAR_CONTENT); 2916 } 2917 2918 /** 2919 * Return a new picture that captures the current display of the webview. 2920 * This is a copy of the display, and will be unaffected if the webview 2921 * later loads a different URL. 2922 * 2923 * @return a picture containing the current contents of the view. Note this 2924 * picture is of the entire document, and is not restricted to the 2925 * bounds of the view. 2926 */ 2927 public Picture capturePicture() { 2928 checkThread(); 2929 if (mNativeClass == 0) return null; 2930 Picture result = new Picture(); 2931 nativeCopyBaseContentToPicture(result); 2932 return result; 2933 } 2934 2935 /** 2936 * Return true if the browser is displaying a TextView for text input. 2937 */ 2938 private boolean inEditingMode() { 2939 return mWebTextView != null && mWebTextView.getParent() != null; 2940 } 2941 2942 /** 2943 * Remove the WebTextView. 2944 */ 2945 private void clearTextEntry() { 2946 if (inEditingMode()) { 2947 mWebTextView.remove(); 2948 } else { 2949 // The keyboard may be open with the WebView as the served view 2950 hideSoftKeyboard(); 2951 } 2952 } 2953 2954 /** 2955 * Return the current scale of the WebView 2956 * @return The current scale. 2957 */ 2958 public float getScale() { 2959 checkThread(); 2960 return mZoomManager.getScale(); 2961 } 2962 2963 /** 2964 * Compute the reading level scale of the WebView 2965 * @param scale The current scale. 2966 * @return The reading level scale. 2967 */ 2968 /*package*/ float computeReadingLevelScale(float scale) { 2969 return mZoomManager.computeReadingLevelScale(scale); 2970 } 2971 2972 /** 2973 * Set the initial scale for the WebView. 0 means default. If 2974 * {@link WebSettings#getUseWideViewPort()} is true, it zooms out all the 2975 * way. Otherwise it starts with 100%. If initial scale is greater than 0, 2976 * WebView starts with this value as initial scale. 2977 * Please note that unlike the scale properties in the viewport meta tag, 2978 * this method doesn't take the screen density into account. 2979 * 2980 * @param scaleInPercent The initial scale in percent. 2981 */ 2982 public void setInitialScale(int scaleInPercent) { 2983 checkThread(); 2984 mZoomManager.setInitialScaleInPercent(scaleInPercent); 2985 } 2986 2987 /** 2988 * Invoke the graphical zoom picker widget for this WebView. This will 2989 * result in the zoom widget appearing on the screen to control the zoom 2990 * level of this WebView. 2991 */ 2992 public void invokeZoomPicker() { 2993 checkThread(); 2994 if (!getSettings().supportZoom()) { 2995 Log.w(LOGTAG, "This WebView doesn't support zoom."); 2996 return; 2997 } 2998 clearHelpers(); 2999 mZoomManager.invokeZoomPicker(); 3000 } 3001 3002 /** 3003 * Return a HitTestResult based on the current cursor node. If a HTML::a tag 3004 * is found and the anchor has a non-JavaScript url, the HitTestResult type 3005 * is set to SRC_ANCHOR_TYPE and the url is set in the "extra" field. If the 3006 * anchor does not have a url or if it is a JavaScript url, the type will 3007 * be UNKNOWN_TYPE and the url has to be retrieved through 3008 * {@link #requestFocusNodeHref} asynchronously. If a HTML::img tag is 3009 * found, the HitTestResult type is set to IMAGE_TYPE and the url is set in 3010 * the "extra" field. A type of 3011 * SRC_IMAGE_ANCHOR_TYPE indicates an anchor with a url that has an image as 3012 * a child node. If a phone number is found, the HitTestResult type is set 3013 * to PHONE_TYPE and the phone number is set in the "extra" field of 3014 * HitTestResult. If a map address is found, the HitTestResult type is set 3015 * to GEO_TYPE and the address is set in the "extra" field of HitTestResult. 3016 * If an email address is found, the HitTestResult type is set to EMAIL_TYPE 3017 * and the email is set in the "extra" field of HitTestResult. Otherwise, 3018 * HitTestResult type is set to UNKNOWN_TYPE. 3019 */ 3020 public HitTestResult getHitTestResult() { 3021 checkThread(); 3022 return hitTestResult(mInitialHitTestResult); 3023 } 3024 3025 private HitTestResult hitTestResult(HitTestResult fallback) { 3026 if (mNativeClass == 0 || sDisableNavcache) { 3027 return fallback; 3028 } 3029 3030 HitTestResult result = new HitTestResult(); 3031 if (nativeHasCursorNode()) { 3032 if (nativeCursorIsTextInput()) { 3033 result.setType(HitTestResult.EDIT_TEXT_TYPE); 3034 } else { 3035 String text = nativeCursorText(); 3036 if (text != null) { 3037 if (text.startsWith(SCHEME_TEL)) { 3038 result.setType(HitTestResult.PHONE_TYPE); 3039 result.setExtra(URLDecoder.decode(text 3040 .substring(SCHEME_TEL.length()))); 3041 } else if (text.startsWith(SCHEME_MAILTO)) { 3042 result.setType(HitTestResult.EMAIL_TYPE); 3043 result.setExtra(text.substring(SCHEME_MAILTO.length())); 3044 } else if (text.startsWith(SCHEME_GEO)) { 3045 result.setType(HitTestResult.GEO_TYPE); 3046 result.setExtra(URLDecoder.decode(text 3047 .substring(SCHEME_GEO.length()))); 3048 } else if (nativeCursorIsAnchor()) { 3049 result.setType(HitTestResult.SRC_ANCHOR_TYPE); 3050 result.setExtra(text); 3051 } 3052 } 3053 } 3054 } else if (fallback != null) { 3055 /* If webkit causes a rebuild while the long press is in progress, 3056 * the cursor node may be reset, even if it is still around. This 3057 * uses the cursor node saved when the touch began. Since the 3058 * nativeImageURI below only changes the result if it is successful, 3059 * this uses the data beneath the touch if available or the original 3060 * tap data otherwise. 3061 */ 3062 Log.v(LOGTAG, "hitTestResult use fallback"); 3063 result = fallback; 3064 } 3065 int type = result.getType(); 3066 if (type == HitTestResult.UNKNOWN_TYPE 3067 || type == HitTestResult.SRC_ANCHOR_TYPE) { 3068 // Now check to see if it is an image. 3069 int contentX = viewToContentX(mLastTouchX + mScrollX); 3070 int contentY = viewToContentY(mLastTouchY + mScrollY); 3071 String text = nativeImageURI(contentX, contentY); 3072 if (text != null) { 3073 result.setType(type == HitTestResult.UNKNOWN_TYPE ? 3074 HitTestResult.IMAGE_TYPE : 3075 HitTestResult.SRC_IMAGE_ANCHOR_TYPE); 3076 result.setExtra(text); 3077 } 3078 } 3079 return result; 3080 } 3081 3082 int getBlockLeftEdge(int x, int y, float readingScale) { 3083 if (!sDisableNavcache) { 3084 return nativeGetBlockLeftEdge(x, y, readingScale); 3085 } 3086 3087 float invReadingScale = 1.0f / readingScale; 3088 int readingWidth = (int) (getViewWidth() * invReadingScale); 3089 int left = NO_LEFTEDGE; 3090 if (mFocusedNode != null) { 3091 final int length = mFocusedNode.mEnclosingParentRects.length; 3092 for (int i = 0; i < length; i++) { 3093 Rect rect = mFocusedNode.mEnclosingParentRects[i]; 3094 if (rect.width() < mFocusedNode.mHitTestSlop) { 3095 // ignore bounding boxes that are too small 3096 continue; 3097 } else if (left != NO_LEFTEDGE && rect.width() > readingWidth) { 3098 // stop when bounding box doesn't fit the screen width 3099 // at reading scale 3100 break; 3101 } 3102 3103 left = rect.left; 3104 } 3105 } 3106 3107 return left; 3108 } 3109 3110 // Called by JNI when the DOM has changed the focus. Clear the focus so 3111 // that new keys will go to the newly focused field 3112 private void domChangedFocus() { 3113 if (inEditingMode()) { 3114 mPrivateHandler.obtainMessage(DOM_FOCUS_CHANGED).sendToTarget(); 3115 } 3116 } 3117 /** 3118 * Request the anchor or image element URL at the last tapped point. 3119 * If hrefMsg is null, this method returns immediately and does not 3120 * dispatch hrefMsg to its target. If the tapped point hits an image, 3121 * an anchor, or an image in an anchor, the message associates 3122 * strings in named keys in its data. The value paired with the key 3123 * may be an empty string. 3124 * 3125 * @param hrefMsg This message will be dispatched with the result of the 3126 * request. The message data contains three keys: 3127 * - "url" returns the anchor's href attribute. 3128 * - "title" returns the anchor's text. 3129 * - "src" returns the image's src attribute. 3130 */ 3131 public void requestFocusNodeHref(Message hrefMsg) { 3132 checkThread(); 3133 if (hrefMsg == null) { 3134 return; 3135 } 3136 int contentX = viewToContentX(mLastTouchX + mScrollX); 3137 int contentY = viewToContentY(mLastTouchY + mScrollY); 3138 if (mFocusedNode != null && mFocusedNode.mHitTestX == contentX 3139 && mFocusedNode.mHitTestY == contentY) { 3140 hrefMsg.getData().putString(FocusNodeHref.URL, mFocusedNode.mLinkUrl); 3141 hrefMsg.getData().putString(FocusNodeHref.TITLE, mFocusedNode.mAnchorText); 3142 hrefMsg.getData().putString(FocusNodeHref.SRC, mFocusedNode.mImageUrl); 3143 hrefMsg.sendToTarget(); 3144 return; 3145 } 3146 if (nativeHasCursorNode()) { 3147 Rect cursorBounds = nativeGetCursorRingBounds(); 3148 if (!cursorBounds.contains(contentX, contentY)) { 3149 int slop = viewToContentDimension(mNavSlop); 3150 cursorBounds.inset(-slop, -slop); 3151 if (cursorBounds.contains(contentX, contentY)) { 3152 contentX = cursorBounds.centerX(); 3153 contentY = cursorBounds.centerY(); 3154 } 3155 } 3156 } 3157 mWebViewCore.sendMessage(EventHub.REQUEST_CURSOR_HREF, 3158 contentX, contentY, hrefMsg); 3159 } 3160 3161 /** 3162 * Request the url of the image last touched by the user. msg will be sent 3163 * to its target with a String representing the url as its object. 3164 * 3165 * @param msg This message will be dispatched with the result of the request 3166 * as the data member with "url" as key. The result can be null. 3167 */ 3168 public void requestImageRef(Message msg) { 3169 checkThread(); 3170 if (0 == mNativeClass) return; // client isn't initialized 3171 int contentX = viewToContentX(mLastTouchX + mScrollX); 3172 int contentY = viewToContentY(mLastTouchY + mScrollY); 3173 String ref = nativeImageURI(contentX, contentY); 3174 Bundle data = msg.getData(); 3175 data.putString("url", ref); 3176 msg.setData(data); 3177 msg.sendToTarget(); 3178 } 3179 3180 static int pinLoc(int x, int viewMax, int docMax) { 3181// Log.d(LOGTAG, "-- pinLoc " + x + " " + viewMax + " " + docMax); 3182 if (docMax < viewMax) { // the doc has room on the sides for "blank" 3183 // pin the short document to the top/left of the screen 3184 x = 0; 3185// Log.d(LOGTAG, "--- center " + x); 3186 } else if (x < 0) { 3187 x = 0; 3188// Log.d(LOGTAG, "--- zero"); 3189 } else if (x + viewMax > docMax) { 3190 x = docMax - viewMax; 3191// Log.d(LOGTAG, "--- pin " + x); 3192 } 3193 return x; 3194 } 3195 3196 // Expects x in view coordinates 3197 int pinLocX(int x) { 3198 if (mInOverScrollMode) return x; 3199 return pinLoc(x, getViewWidth(), computeRealHorizontalScrollRange()); 3200 } 3201 3202 // Expects y in view coordinates 3203 int pinLocY(int y) { 3204 if (mInOverScrollMode) return y; 3205 return pinLoc(y, getViewHeightWithTitle(), 3206 computeRealVerticalScrollRange() + getTitleHeight()); 3207 } 3208 3209 /** 3210 * A title bar which is embedded in this WebView, and scrolls along with it 3211 * vertically, but not horizontally. 3212 */ 3213 private View mTitleBar; 3214 3215 /** 3216 * the title bar rendering gravity 3217 */ 3218 private int mTitleGravity; 3219 3220 /** 3221 * Add or remove a title bar to be embedded into the WebView, and scroll 3222 * along with it vertically, while remaining in view horizontally. Pass 3223 * null to remove the title bar from the WebView, and return to drawing 3224 * the WebView normally without translating to account for the title bar. 3225 * @hide 3226 */ 3227 public void setEmbeddedTitleBar(View v) { 3228 if (mTitleBar == v) return; 3229 if (mTitleBar != null) { 3230 removeView(mTitleBar); 3231 } 3232 if (null != v) { 3233 addView(v, new AbsoluteLayout.LayoutParams( 3234 ViewGroup.LayoutParams.MATCH_PARENT, 3235 ViewGroup.LayoutParams.WRAP_CONTENT, 0, 0)); 3236 } 3237 mTitleBar = v; 3238 } 3239 3240 /** 3241 * Set where to render the embedded title bar 3242 * NO_GRAVITY at the top of the page 3243 * TOP at the top of the screen 3244 * @hide 3245 */ 3246 public void setTitleBarGravity(int gravity) { 3247 mTitleGravity = gravity; 3248 // force refresh 3249 invalidate(); 3250 } 3251 3252 /** 3253 * Given a distance in view space, convert it to content space. Note: this 3254 * does not reflect translation, just scaling, so this should not be called 3255 * with coordinates, but should be called for dimensions like width or 3256 * height. 3257 */ 3258 private int viewToContentDimension(int d) { 3259 return Math.round(d * mZoomManager.getInvScale()); 3260 } 3261 3262 /** 3263 * Given an x coordinate in view space, convert it to content space. Also 3264 * may be used for absolute heights (such as for the WebTextView's 3265 * textSize, which is unaffected by the height of the title bar). 3266 */ 3267 /*package*/ int viewToContentX(int x) { 3268 return viewToContentDimension(x); 3269 } 3270 3271 /** 3272 * Given a y coordinate in view space, convert it to content space. 3273 * Takes into account the height of the title bar if there is one 3274 * embedded into the WebView. 3275 */ 3276 /*package*/ int viewToContentY(int y) { 3277 return viewToContentDimension(y - getTitleHeight()); 3278 } 3279 3280 /** 3281 * Given a x coordinate in view space, convert it to content space. 3282 * Returns the result as a float. 3283 */ 3284 private float viewToContentXf(int x) { 3285 return x * mZoomManager.getInvScale(); 3286 } 3287 3288 /** 3289 * Given a y coordinate in view space, convert it to content space. 3290 * Takes into account the height of the title bar if there is one 3291 * embedded into the WebView. Returns the result as a float. 3292 */ 3293 private float viewToContentYf(int y) { 3294 return (y - getTitleHeight()) * mZoomManager.getInvScale(); 3295 } 3296 3297 /** 3298 * Given a distance in content space, convert it to view space. Note: this 3299 * does not reflect translation, just scaling, so this should not be called 3300 * with coordinates, but should be called for dimensions like width or 3301 * height. 3302 */ 3303 /*package*/ int contentToViewDimension(int d) { 3304 return Math.round(d * mZoomManager.getScale()); 3305 } 3306 3307 /** 3308 * Given an x coordinate in content space, convert it to view 3309 * space. 3310 */ 3311 /*package*/ int contentToViewX(int x) { 3312 return contentToViewDimension(x); 3313 } 3314 3315 /** 3316 * Given a y coordinate in content space, convert it to view 3317 * space. Takes into account the height of the title bar. 3318 */ 3319 /*package*/ int contentToViewY(int y) { 3320 return contentToViewDimension(y) + getTitleHeight(); 3321 } 3322 3323 private Rect contentToViewRect(Rect x) { 3324 return new Rect(contentToViewX(x.left), contentToViewY(x.top), 3325 contentToViewX(x.right), contentToViewY(x.bottom)); 3326 } 3327 3328 /* To invalidate a rectangle in content coordinates, we need to transform 3329 the rect into view coordinates, so we can then call invalidate(...). 3330 3331 Normally, we would just call contentToView[XY](...), which eventually 3332 calls Math.round(coordinate * mActualScale). However, for invalidates, 3333 we need to account for the slop that occurs with antialiasing. To 3334 address that, we are a little more liberal in the size of the rect that 3335 we invalidate. 3336 3337 This liberal calculation calls floor() for the top/left, and ceil() for 3338 the bottom/right coordinates. This catches the possible extra pixels of 3339 antialiasing that we might have missed with just round(). 3340 */ 3341 3342 // Called by JNI to invalidate the View, given rectangle coordinates in 3343 // content space 3344 private void viewInvalidate(int l, int t, int r, int b) { 3345 final float scale = mZoomManager.getScale(); 3346 final int dy = getTitleHeight(); 3347 invalidate((int)Math.floor(l * scale), 3348 (int)Math.floor(t * scale) + dy, 3349 (int)Math.ceil(r * scale), 3350 (int)Math.ceil(b * scale) + dy); 3351 } 3352 3353 // Called by JNI to invalidate the View after a delay, given rectangle 3354 // coordinates in content space 3355 private void viewInvalidateDelayed(long delay, int l, int t, int r, int b) { 3356 final float scale = mZoomManager.getScale(); 3357 final int dy = getTitleHeight(); 3358 postInvalidateDelayed(delay, 3359 (int)Math.floor(l * scale), 3360 (int)Math.floor(t * scale) + dy, 3361 (int)Math.ceil(r * scale), 3362 (int)Math.ceil(b * scale) + dy); 3363 } 3364 3365 private void invalidateContentRect(Rect r) { 3366 viewInvalidate(r.left, r.top, r.right, r.bottom); 3367 } 3368 3369 // stop the scroll animation, and don't let a subsequent fling add 3370 // to the existing velocity 3371 private void abortAnimation() { 3372 mScroller.abortAnimation(); 3373 mLastVelocity = 0; 3374 } 3375 3376 /* call from webcoreview.draw(), so we're still executing in the UI thread 3377 */ 3378 private void recordNewContentSize(int w, int h, boolean updateLayout) { 3379 3380 // premature data from webkit, ignore 3381 if ((w | h) == 0) { 3382 return; 3383 } 3384 3385 // don't abort a scroll animation if we didn't change anything 3386 if (mContentWidth != w || mContentHeight != h) { 3387 // record new dimensions 3388 mContentWidth = w; 3389 mContentHeight = h; 3390 // If history Picture is drawn, don't update scroll. They will be 3391 // updated when we get out of that mode. 3392 if (!mDrawHistory) { 3393 // repin our scroll, taking into account the new content size 3394 updateScrollCoordinates(pinLocX(mScrollX), pinLocY(mScrollY)); 3395 if (!mScroller.isFinished()) { 3396 // We are in the middle of a scroll. Repin the final scroll 3397 // position. 3398 mScroller.setFinalX(pinLocX(mScroller.getFinalX())); 3399 mScroller.setFinalY(pinLocY(mScroller.getFinalY())); 3400 } 3401 } 3402 } 3403 contentSizeChanged(updateLayout); 3404 } 3405 3406 // Used to avoid sending many visible rect messages. 3407 private Rect mLastVisibleRectSent = new Rect(); 3408 private Rect mLastGlobalRect = new Rect(); 3409 private Rect mVisibleRect = new Rect(); 3410 private Rect mGlobalVisibleRect = new Rect(); 3411 private Point mScrollOffset = new Point(); 3412 3413 Rect sendOurVisibleRect() { 3414 if (mZoomManager.isPreventingWebkitUpdates()) return mLastVisibleRectSent; 3415 calcOurContentVisibleRect(mVisibleRect); 3416 // Rect.equals() checks for null input. 3417 if (!mVisibleRect.equals(mLastVisibleRectSent)) { 3418 if (!mBlockWebkitViewMessages) { 3419 mScrollOffset.set(mVisibleRect.left, mVisibleRect.top); 3420 mWebViewCore.removeMessages(EventHub.SET_SCROLL_OFFSET); 3421 mWebViewCore.sendMessage(EventHub.SET_SCROLL_OFFSET, 3422 nativeMoveGeneration(), mSendScrollEvent ? 1 : 0, mScrollOffset); 3423 } 3424 mLastVisibleRectSent.set(mVisibleRect); 3425 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); 3426 } 3427 if (getGlobalVisibleRect(mGlobalVisibleRect) 3428 && !mGlobalVisibleRect.equals(mLastGlobalRect)) { 3429 if (DebugFlags.WEB_VIEW) { 3430 Log.v(LOGTAG, "sendOurVisibleRect=(" + mGlobalVisibleRect.left + "," 3431 + mGlobalVisibleRect.top + ",r=" + mGlobalVisibleRect.right + ",b=" 3432 + mGlobalVisibleRect.bottom); 3433 } 3434 // TODO: the global offset is only used by windowRect() 3435 // in ChromeClientAndroid ; other clients such as touch 3436 // and mouse events could return view + screen relative points. 3437 if (!mBlockWebkitViewMessages) { 3438 mWebViewCore.sendMessage(EventHub.SET_GLOBAL_BOUNDS, mGlobalVisibleRect); 3439 } 3440 mLastGlobalRect.set(mGlobalVisibleRect); 3441 } 3442 return mVisibleRect; 3443 } 3444 3445 private Point mGlobalVisibleOffset = new Point(); 3446 // Sets r to be the visible rectangle of our webview in view coordinates 3447 private void calcOurVisibleRect(Rect r) { 3448 getGlobalVisibleRect(r, mGlobalVisibleOffset); 3449 r.offset(-mGlobalVisibleOffset.x, -mGlobalVisibleOffset.y); 3450 } 3451 3452 // Sets r to be our visible rectangle in content coordinates 3453 private void calcOurContentVisibleRect(Rect r) { 3454 calcOurVisibleRect(r); 3455 r.left = viewToContentX(r.left); 3456 // viewToContentY will remove the total height of the title bar. Add 3457 // the visible height back in to account for the fact that if the title 3458 // bar is partially visible, the part of the visible rect which is 3459 // displaying our content is displaced by that amount. 3460 r.top = viewToContentY(r.top + getVisibleTitleHeightImpl()); 3461 r.right = viewToContentX(r.right); 3462 r.bottom = viewToContentY(r.bottom); 3463 } 3464 3465 private Rect mContentVisibleRect = new Rect(); 3466 // Sets r to be our visible rectangle in content coordinates. We use this 3467 // method on the native side to compute the position of the fixed layers. 3468 // Uses floating coordinates (necessary to correctly place elements when 3469 // the scale factor is not 1) 3470 private void calcOurContentVisibleRectF(RectF r) { 3471 calcOurVisibleRect(mContentVisibleRect); 3472 r.left = viewToContentXf(mContentVisibleRect.left); 3473 // viewToContentY will remove the total height of the title bar. Add 3474 // the visible height back in to account for the fact that if the title 3475 // bar is partially visible, the part of the visible rect which is 3476 // displaying our content is displaced by that amount. 3477 r.top = viewToContentYf(mContentVisibleRect.top + getVisibleTitleHeightImpl()); 3478 r.right = viewToContentXf(mContentVisibleRect.right); 3479 r.bottom = viewToContentYf(mContentVisibleRect.bottom); 3480 } 3481 3482 static class ViewSizeData { 3483 int mWidth; 3484 int mHeight; 3485 float mHeightWidthRatio; 3486 int mActualViewHeight; 3487 int mTextWrapWidth; 3488 int mAnchorX; 3489 int mAnchorY; 3490 float mScale; 3491 boolean mIgnoreHeight; 3492 } 3493 3494 /** 3495 * Compute unzoomed width and height, and if they differ from the last 3496 * values we sent, send them to webkit (to be used as new viewport) 3497 * 3498 * @param force ensures that the message is sent to webkit even if the width 3499 * or height has not changed since the last message 3500 * 3501 * @return true if new values were sent 3502 */ 3503 boolean sendViewSizeZoom(boolean force) { 3504 if (mBlockWebkitViewMessages) return false; 3505 if (mZoomManager.isPreventingWebkitUpdates()) return false; 3506 3507 int viewWidth = getViewWidth(); 3508 int newWidth = Math.round(viewWidth * mZoomManager.getInvScale()); 3509 // This height could be fixed and be different from actual visible height. 3510 int viewHeight = getViewHeightWithTitle() - getTitleHeight(); 3511 int newHeight = Math.round(viewHeight * mZoomManager.getInvScale()); 3512 // Make the ratio more accurate than (newHeight / newWidth), since the 3513 // latter both are calculated and rounded. 3514 float heightWidthRatio = (float) viewHeight / viewWidth; 3515 /* 3516 * Because the native side may have already done a layout before the 3517 * View system was able to measure us, we have to send a height of 0 to 3518 * remove excess whitespace when we grow our width. This will trigger a 3519 * layout and a change in content size. This content size change will 3520 * mean that contentSizeChanged will either call this method directly or 3521 * indirectly from onSizeChanged. 3522 */ 3523 if (newWidth > mLastWidthSent && mWrapContent) { 3524 newHeight = 0; 3525 heightWidthRatio = 0; 3526 } 3527 // Actual visible content height. 3528 int actualViewHeight = Math.round(getViewHeight() * mZoomManager.getInvScale()); 3529 // Avoid sending another message if the dimensions have not changed. 3530 if (newWidth != mLastWidthSent || newHeight != mLastHeightSent || force || 3531 actualViewHeight != mLastActualHeightSent) { 3532 ViewSizeData data = new ViewSizeData(); 3533 data.mWidth = newWidth; 3534 data.mHeight = newHeight; 3535 data.mHeightWidthRatio = heightWidthRatio; 3536 data.mActualViewHeight = actualViewHeight; 3537 data.mTextWrapWidth = Math.round(viewWidth / mZoomManager.getTextWrapScale()); 3538 data.mScale = mZoomManager.getScale(); 3539 data.mIgnoreHeight = mZoomManager.isFixedLengthAnimationInProgress() 3540 && !mHeightCanMeasure; 3541 data.mAnchorX = mZoomManager.getDocumentAnchorX(); 3542 data.mAnchorY = mZoomManager.getDocumentAnchorY(); 3543 mWebViewCore.sendMessage(EventHub.VIEW_SIZE_CHANGED, data); 3544 mLastWidthSent = newWidth; 3545 mLastHeightSent = newHeight; 3546 mLastActualHeightSent = actualViewHeight; 3547 mZoomManager.clearDocumentAnchor(); 3548 return true; 3549 } 3550 return false; 3551 } 3552 3553 /** 3554 * Update the double-tap zoom. 3555 */ 3556 /* package */ void updateDoubleTapZoom(int doubleTapZoom) { 3557 mZoomManager.updateDoubleTapZoom(doubleTapZoom); 3558 } 3559 3560 private int computeRealHorizontalScrollRange() { 3561 if (mDrawHistory) { 3562 return mHistoryWidth; 3563 } else { 3564 // to avoid rounding error caused unnecessary scrollbar, use floor 3565 return (int) Math.floor(mContentWidth * mZoomManager.getScale()); 3566 } 3567 } 3568 3569 @Override 3570 protected int computeHorizontalScrollRange() { 3571 int range = computeRealHorizontalScrollRange(); 3572 3573 // Adjust reported range if overscrolled to compress the scroll bars 3574 final int scrollX = mScrollX; 3575 final int overscrollRight = computeMaxScrollX(); 3576 if (scrollX < 0) { 3577 range -= scrollX; 3578 } else if (scrollX > overscrollRight) { 3579 range += scrollX - overscrollRight; 3580 } 3581 3582 return range; 3583 } 3584 3585 @Override 3586 protected int computeHorizontalScrollOffset() { 3587 return Math.max(mScrollX, 0); 3588 } 3589 3590 private int computeRealVerticalScrollRange() { 3591 if (mDrawHistory) { 3592 return mHistoryHeight; 3593 } else { 3594 // to avoid rounding error caused unnecessary scrollbar, use floor 3595 return (int) Math.floor(mContentHeight * mZoomManager.getScale()); 3596 } 3597 } 3598 3599 @Override 3600 protected int computeVerticalScrollRange() { 3601 int range = computeRealVerticalScrollRange(); 3602 3603 // Adjust reported range if overscrolled to compress the scroll bars 3604 final int scrollY = mScrollY; 3605 final int overscrollBottom = computeMaxScrollY(); 3606 if (scrollY < 0) { 3607 range -= scrollY; 3608 } else if (scrollY > overscrollBottom) { 3609 range += scrollY - overscrollBottom; 3610 } 3611 3612 return range; 3613 } 3614 3615 @Override 3616 protected int computeVerticalScrollOffset() { 3617 return Math.max(mScrollY - getTitleHeight(), 0); 3618 } 3619 3620 @Override 3621 protected int computeVerticalScrollExtent() { 3622 return getViewHeight(); 3623 } 3624 3625 /** @hide */ 3626 @Override 3627 protected void onDrawVerticalScrollBar(Canvas canvas, 3628 Drawable scrollBar, 3629 int l, int t, int r, int b) { 3630 if (mScrollY < 0) { 3631 t -= mScrollY; 3632 } 3633 scrollBar.setBounds(l, t + getVisibleTitleHeightImpl(), r, b); 3634 scrollBar.draw(canvas); 3635 } 3636 3637 @Override 3638 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, 3639 boolean clampedY) { 3640 // Special-case layer scrolling so that we do not trigger normal scroll 3641 // updating. 3642 if (mTouchMode == TOUCH_DRAG_LAYER_MODE) { 3643 scrollLayerTo(scrollX, scrollY); 3644 return; 3645 } 3646 mInOverScrollMode = false; 3647 int maxX = computeMaxScrollX(); 3648 int maxY = computeMaxScrollY(); 3649 if (maxX == 0) { 3650 // do not over scroll x if the page just fits the screen 3651 scrollX = pinLocX(scrollX); 3652 } else if (scrollX < 0 || scrollX > maxX) { 3653 mInOverScrollMode = true; 3654 } 3655 if (scrollY < 0 || scrollY > maxY) { 3656 mInOverScrollMode = true; 3657 } 3658 3659 int oldX = mScrollX; 3660 int oldY = mScrollY; 3661 3662 super.scrollTo(scrollX, scrollY); 3663 3664 if (mOverScrollGlow != null) { 3665 mOverScrollGlow.pullGlow(mScrollX, mScrollY, oldX, oldY, maxX, maxY); 3666 } 3667 } 3668 3669 /** 3670 * Get the url for the current page. This is not always the same as the url 3671 * passed to WebViewClient.onPageStarted because although the load for 3672 * that url has begun, the current page may not have changed. 3673 * @return The url for the current page. 3674 */ 3675 public String getUrl() { 3676 checkThread(); 3677 WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); 3678 return h != null ? h.getUrl() : null; 3679 } 3680 3681 /** 3682 * Get the original url for the current page. This is not always the same 3683 * as the url passed to WebViewClient.onPageStarted because although the 3684 * load for that url has begun, the current page may not have changed. 3685 * Also, there may have been redirects resulting in a different url to that 3686 * originally requested. 3687 * @return The url that was originally requested for the current page. 3688 */ 3689 public String getOriginalUrl() { 3690 checkThread(); 3691 WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); 3692 return h != null ? h.getOriginalUrl() : null; 3693 } 3694 3695 /** 3696 * Get the title for the current page. This is the title of the current page 3697 * until WebViewClient.onReceivedTitle is called. 3698 * @return The title for the current page. 3699 */ 3700 public String getTitle() { 3701 checkThread(); 3702 WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); 3703 return h != null ? h.getTitle() : null; 3704 } 3705 3706 /** 3707 * Get the favicon for the current page. This is the favicon of the current 3708 * page until WebViewClient.onReceivedIcon is called. 3709 * @return The favicon for the current page. 3710 */ 3711 public Bitmap getFavicon() { 3712 checkThread(); 3713 WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); 3714 return h != null ? h.getFavicon() : null; 3715 } 3716 3717 /** 3718 * Get the touch icon url for the apple-touch-icon <link> element, or 3719 * a URL on this site's server pointing to the standard location of a 3720 * touch icon. 3721 * @hide 3722 */ 3723 public String getTouchIconUrl() { 3724 WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); 3725 return h != null ? h.getTouchIconUrl() : null; 3726 } 3727 3728 /** 3729 * Get the progress for the current page. 3730 * @return The progress for the current page between 0 and 100. 3731 */ 3732 public int getProgress() { 3733 checkThread(); 3734 return mCallbackProxy.getProgress(); 3735 } 3736 3737 /** 3738 * @return the height of the HTML content. 3739 */ 3740 public int getContentHeight() { 3741 checkThread(); 3742 return mContentHeight; 3743 } 3744 3745 /** 3746 * @return the width of the HTML content. 3747 * @hide 3748 */ 3749 public int getContentWidth() { 3750 return mContentWidth; 3751 } 3752 3753 /** 3754 * @hide 3755 */ 3756 public int getPageBackgroundColor() { 3757 return nativeGetBackgroundColor(); 3758 } 3759 3760 /** 3761 * Pause all layout, parsing, and JavaScript timers for all webviews. This 3762 * is a global requests, not restricted to just this webview. This can be 3763 * useful if the application has been paused. 3764 */ 3765 public void pauseTimers() { 3766 checkThread(); 3767 mWebViewCore.sendMessage(EventHub.PAUSE_TIMERS); 3768 } 3769 3770 /** 3771 * Resume all layout, parsing, and JavaScript timers for all webviews. 3772 * This will resume dispatching all timers. 3773 */ 3774 public void resumeTimers() { 3775 checkThread(); 3776 mWebViewCore.sendMessage(EventHub.RESUME_TIMERS); 3777 } 3778 3779 /** 3780 * Call this to pause any extra processing associated with this WebView and 3781 * its associated DOM, plugins, JavaScript etc. For example, if the WebView 3782 * is taken offscreen, this could be called to reduce unnecessary CPU or 3783 * network traffic. When the WebView is again "active", call onResume(). 3784 * 3785 * Note that this differs from pauseTimers(), which affects all WebViews. 3786 */ 3787 public void onPause() { 3788 checkThread(); 3789 if (!mIsPaused) { 3790 mIsPaused = true; 3791 mWebViewCore.sendMessage(EventHub.ON_PAUSE); 3792 // We want to pause the current playing video when switching out 3793 // from the current WebView/tab. 3794 if (mHTML5VideoViewProxy != null) { 3795 mHTML5VideoViewProxy.pauseAndDispatch(); 3796 } 3797 if (mNativeClass != 0) { 3798 nativeSetPauseDrawing(mNativeClass, true); 3799 } 3800 3801 cancelSelectDialog(); 3802 WebCoreThreadWatchdog.pause(); 3803 } 3804 } 3805 3806 @Override 3807 protected void onWindowVisibilityChanged(int visibility) { 3808 super.onWindowVisibilityChanged(visibility); 3809 updateDrawingState(); 3810 } 3811 3812 void updateDrawingState() { 3813 if (mNativeClass == 0 || mIsPaused) return; 3814 if (getWindowVisibility() != VISIBLE) { 3815 nativeSetPauseDrawing(mNativeClass, true); 3816 } else if (getVisibility() != VISIBLE) { 3817 nativeSetPauseDrawing(mNativeClass, true); 3818 } else { 3819 nativeSetPauseDrawing(mNativeClass, false); 3820 } 3821 } 3822 3823 /** 3824 * Call this to resume a WebView after a previous call to onPause(). 3825 */ 3826 public void onResume() { 3827 checkThread(); 3828 if (mIsPaused) { 3829 mIsPaused = false; 3830 mWebViewCore.sendMessage(EventHub.ON_RESUME); 3831 if (mNativeClass != 0) { 3832 nativeSetPauseDrawing(mNativeClass, false); 3833 } 3834 } 3835 // Ensure that the watchdog has a currently valid Context to be able to display 3836 // a prompt dialog. For example, if the Activity was finished whilst the WebCore 3837 // thread was blocked and the Activity is started again, we may reuse the blocked 3838 // thread, but we'll have a new Activity. 3839 WebCoreThreadWatchdog.updateContext(mContext); 3840 // We get a call to onResume for new WebViews (i.e. mIsPaused will be false). We need 3841 // to ensure that the Watchdog thread is running for the new WebView, so call 3842 // it outside the if block above. 3843 WebCoreThreadWatchdog.resume(); 3844 } 3845 3846 /** 3847 * Returns true if the view is paused, meaning onPause() was called. Calling 3848 * onResume() sets the paused state back to false. 3849 * @hide 3850 */ 3851 public boolean isPaused() { 3852 return mIsPaused; 3853 } 3854 3855 /** 3856 * Call this to inform the view that memory is low so that it can 3857 * free any available memory. 3858 */ 3859 public void freeMemory() { 3860 checkThread(); 3861 mWebViewCore.sendMessage(EventHub.FREE_MEMORY); 3862 } 3863 3864 /** 3865 * Clear the resource cache. Note that the cache is per-application, so 3866 * this will clear the cache for all WebViews used. 3867 * 3868 * @param includeDiskFiles If false, only the RAM cache is cleared. 3869 */ 3870 public void clearCache(boolean includeDiskFiles) { 3871 checkThread(); 3872 // Note: this really needs to be a static method as it clears cache for all 3873 // WebView. But we need mWebViewCore to send message to WebCore thread, so 3874 // we can't make this static. 3875 mWebViewCore.sendMessage(EventHub.CLEAR_CACHE, 3876 includeDiskFiles ? 1 : 0, 0); 3877 } 3878 3879 /** 3880 * Make sure that clearing the form data removes the adapter from the 3881 * currently focused textfield if there is one. 3882 */ 3883 public void clearFormData() { 3884 checkThread(); 3885 if (inEditingMode()) { 3886 mWebTextView.setAdapterCustom(null); 3887 } 3888 } 3889 3890 /** 3891 * Tell the WebView to clear its internal back/forward list. 3892 */ 3893 public void clearHistory() { 3894 checkThread(); 3895 mCallbackProxy.getBackForwardList().setClearPending(); 3896 mWebViewCore.sendMessage(EventHub.CLEAR_HISTORY); 3897 } 3898 3899 /** 3900 * Clear the SSL preferences table stored in response to proceeding with SSL 3901 * certificate errors. 3902 */ 3903 public void clearSslPreferences() { 3904 checkThread(); 3905 mWebViewCore.sendMessage(EventHub.CLEAR_SSL_PREF_TABLE); 3906 } 3907 3908 /** 3909 * Return the WebBackForwardList for this WebView. This contains the 3910 * back/forward list for use in querying each item in the history stack. 3911 * This is a copy of the private WebBackForwardList so it contains only a 3912 * snapshot of the current state. Multiple calls to this method may return 3913 * different objects. The object returned from this method will not be 3914 * updated to reflect any new state. 3915 */ 3916 public WebBackForwardList copyBackForwardList() { 3917 checkThread(); 3918 return mCallbackProxy.getBackForwardList().clone(); 3919 } 3920 3921 /* 3922 * Highlight and scroll to the next occurance of String in findAll. 3923 * Wraps the page infinitely, and scrolls. Must be called after 3924 * calling findAll. 3925 * 3926 * @param forward Direction to search. 3927 */ 3928 public void findNext(boolean forward) { 3929 checkThread(); 3930 if (0 == mNativeClass) return; // client isn't initialized 3931 mWebViewCore.sendMessage(EventHub.FIND_NEXT, forward ? 1 : 0); 3932 } 3933 3934 /* 3935 * Find all instances of find on the page and highlight them. 3936 * @param find String to find. 3937 * @return int The number of occurances of the String "find" 3938 * that were found. 3939 */ 3940 public int findAll(String find) { 3941 return findAllBody(find, false); 3942 } 3943 3944 /** 3945 * @hide 3946 */ 3947 public void findAllAsync(String find) { 3948 findAllBody(find, true); 3949 } 3950 3951 private int findAllBody(String find, boolean isAsync) { 3952 checkThread(); 3953 if (0 == mNativeClass) return 0; // client isn't initialized 3954 mLastFind = find; 3955 mWebViewCore.removeMessages(EventHub.FIND_ALL); 3956 WebViewCore.FindAllRequest request = new 3957 WebViewCore.FindAllRequest(find); 3958 if (isAsync) { 3959 mWebViewCore.sendMessage(EventHub.FIND_ALL, request); 3960 return 0; // no need to wait for response 3961 } 3962 synchronized(request) { 3963 try { 3964 mWebViewCore.sendMessageAtFrontOfQueue(EventHub.FIND_ALL, 3965 request); 3966 while (request.mMatchCount == -1) { 3967 request.wait(); 3968 } 3969 } 3970 catch (InterruptedException e) { 3971 return 0; 3972 } 3973 } 3974 return request.mMatchCount; 3975 } 3976 3977 /** 3978 * Start an ActionMode for finding text in this WebView. Only works if this 3979 * WebView is attached to the view system. 3980 * @param text If non-null, will be the initial text to search for. 3981 * Otherwise, the last String searched for in this WebView will 3982 * be used to start. 3983 * @param showIme If true, show the IME, assuming the user will begin typing. 3984 * If false and text is non-null, perform a find all. 3985 * @return boolean True if the find dialog is shown, false otherwise. 3986 */ 3987 public boolean showFindDialog(String text, boolean showIme) { 3988 checkThread(); 3989 FindActionModeCallback callback = new FindActionModeCallback(mContext); 3990 if (getParent() == null || startActionMode(callback) == null) { 3991 // Could not start the action mode, so end Find on page 3992 return false; 3993 } 3994 mCachedOverlappingActionModeHeight = -1; 3995 mFindCallback = callback; 3996 setFindIsUp(true); 3997 mFindCallback.setWebView(this); 3998 if (showIme) { 3999 mFindCallback.showSoftInput(); 4000 } else if (text != null) { 4001 mFindCallback.setText(text); 4002 mFindCallback.findAll(); 4003 return true; 4004 } 4005 if (text == null) { 4006 text = mLastFind; 4007 } 4008 if (text != null) { 4009 mFindCallback.setText(text); 4010 mFindCallback.findAll(); 4011 } 4012 return true; 4013 } 4014 4015 /** 4016 * Keep track of the find callback so that we can remove its titlebar if 4017 * necessary. 4018 */ 4019 private FindActionModeCallback mFindCallback; 4020 4021 /** 4022 * Toggle whether the find dialog is showing, for both native and Java. 4023 */ 4024 private void setFindIsUp(boolean isUp) { 4025 mFindIsUp = isUp; 4026 if (0 == mNativeClass) return; // client isn't initialized 4027 nativeSetFindIsUp(isUp); 4028 } 4029 4030 // Used to know whether the find dialog is open. Affects whether 4031 // or not we draw the highlights for matches. 4032 private boolean mFindIsUp; 4033 4034 // Keep track of the last string sent, so we can search again when find is 4035 // reopened. 4036 private String mLastFind; 4037 4038 /** 4039 * Return the first substring consisting of the address of a physical 4040 * location. Currently, only addresses in the United States are detected, 4041 * and consist of: 4042 * - a house number 4043 * - a street name 4044 * - a street type (Road, Circle, etc), either spelled out or abbreviated 4045 * - a city name 4046 * - a state or territory, either spelled out or two-letter abbr. 4047 * - an optional 5 digit or 9 digit zip code. 4048 * 4049 * All names must be correctly capitalized, and the zip code, if present, 4050 * must be valid for the state. The street type must be a standard USPS 4051 * spelling or abbreviation. The state or territory must also be spelled 4052 * or abbreviated using USPS standards. The house number may not exceed 4053 * five digits. 4054 * @param addr The string to search for addresses. 4055 * 4056 * @return the address, or if no address is found, return null. 4057 */ 4058 public static String findAddress(String addr) { 4059 checkThread(); 4060 return findAddress(addr, false); 4061 } 4062 4063 /** 4064 * @hide 4065 * Return the first substring consisting of the address of a physical 4066 * location. Currently, only addresses in the United States are detected, 4067 * and consist of: 4068 * - a house number 4069 * - a street name 4070 * - a street type (Road, Circle, etc), either spelled out or abbreviated 4071 * - a city name 4072 * - a state or territory, either spelled out or two-letter abbr. 4073 * - an optional 5 digit or 9 digit zip code. 4074 * 4075 * Names are optionally capitalized, and the zip code, if present, 4076 * must be valid for the state. The street type must be a standard USPS 4077 * spelling or abbreviation. The state or territory must also be spelled 4078 * or abbreviated using USPS standards. The house number may not exceed 4079 * five digits. 4080 * @param addr The string to search for addresses. 4081 * @param caseInsensitive addr Set to true to make search ignore case. 4082 * 4083 * @return the address, or if no address is found, return null. 4084 */ 4085 public static String findAddress(String addr, boolean caseInsensitive) { 4086 return WebViewCore.nativeFindAddress(addr, caseInsensitive); 4087 } 4088 4089 /* 4090 * Clear the highlighting surrounding text matches created by findAll. 4091 */ 4092 public void clearMatches() { 4093 checkThread(); 4094 if (mNativeClass == 0) 4095 return; 4096 mWebViewCore.removeMessages(EventHub.FIND_ALL); 4097 mWebViewCore.sendMessage(EventHub.FIND_ALL, null); 4098 } 4099 4100 4101 /** 4102 * Called when the find ActionMode ends. 4103 */ 4104 void notifyFindDialogDismissed() { 4105 mFindCallback = null; 4106 mCachedOverlappingActionModeHeight = -1; 4107 if (mWebViewCore == null) { 4108 return; 4109 } 4110 clearMatches(); 4111 setFindIsUp(false); 4112 // Now that the dialog has been removed, ensure that we scroll to a 4113 // location that is not beyond the end of the page. 4114 pinScrollTo(mScrollX, mScrollY, false, 0); 4115 invalidate(); 4116 } 4117 4118 /** 4119 * Query the document to see if it contains any image references. The 4120 * message object will be dispatched with arg1 being set to 1 if images 4121 * were found and 0 if the document does not reference any images. 4122 * @param response The message that will be dispatched with the result. 4123 */ 4124 public void documentHasImages(Message response) { 4125 checkThread(); 4126 if (response == null) { 4127 return; 4128 } 4129 mWebViewCore.sendMessage(EventHub.DOC_HAS_IMAGES, response); 4130 } 4131 4132 /** 4133 * Request the scroller to abort any ongoing animation 4134 * 4135 * @hide 4136 */ 4137 public void stopScroll() { 4138 mScroller.forceFinished(true); 4139 mLastVelocity = 0; 4140 } 4141 4142 @Override 4143 public void computeScroll() { 4144 if (mScroller.computeScrollOffset()) { 4145 int oldX = mScrollX; 4146 int oldY = mScrollY; 4147 int x = mScroller.getCurrX(); 4148 int y = mScroller.getCurrY(); 4149 invalidate(); // So we draw again 4150 4151 if (!mScroller.isFinished()) { 4152 int rangeX = computeMaxScrollX(); 4153 int rangeY = computeMaxScrollY(); 4154 int overflingDistance = mOverflingDistance; 4155 4156 // Use the layer's scroll data if needed. 4157 if (mTouchMode == TOUCH_DRAG_LAYER_MODE) { 4158 oldX = mScrollingLayerRect.left; 4159 oldY = mScrollingLayerRect.top; 4160 rangeX = mScrollingLayerRect.right; 4161 rangeY = mScrollingLayerRect.bottom; 4162 // No overscrolling for layers. 4163 overflingDistance = 0; 4164 } 4165 4166 overScrollBy(x - oldX, y - oldY, oldX, oldY, 4167 rangeX, rangeY, 4168 overflingDistance, overflingDistance, false); 4169 4170 if (mOverScrollGlow != null) { 4171 mOverScrollGlow.absorbGlow(x, y, oldX, oldY, rangeX, rangeY); 4172 } 4173 } else { 4174 if (mTouchMode != TOUCH_DRAG_LAYER_MODE) { 4175 mScrollX = x; 4176 mScrollY = y; 4177 } else { 4178 // Update the layer position instead of WebView. 4179 scrollLayerTo(x, y); 4180 } 4181 abortAnimation(); 4182 nativeSetIsScrolling(false); 4183 if (!mBlockWebkitViewMessages) { 4184 WebViewCore.resumePriority(); 4185 if (!mSelectingText) { 4186 WebViewCore.resumeUpdatePicture(mWebViewCore); 4187 } 4188 } 4189 if (oldX != mScrollX || oldY != mScrollY) { 4190 sendOurVisibleRect(); 4191 } 4192 } 4193 } else { 4194 super.computeScroll(); 4195 } 4196 } 4197 4198 private void scrollLayerTo(int x, int y) { 4199 if (x == mScrollingLayerRect.left && y == mScrollingLayerRect.top) { 4200 return; 4201 } 4202 if (mSelectingText) { 4203 int dx = mScrollingLayerRect.left - x; 4204 int dy = mScrollingLayerRect.top - y; 4205 if (mSelectCursorBaseLayerId == mCurrentScrollingLayerId) { 4206 mSelectCursorBase.offset(dx, dy); 4207 } 4208 if (mSelectCursorExtentLayerId == mCurrentScrollingLayerId) { 4209 mSelectCursorExtent.offset(dx, dy); 4210 } 4211 } 4212 nativeScrollLayer(mCurrentScrollingLayerId, x, y); 4213 mScrollingLayerRect.left = x; 4214 mScrollingLayerRect.top = y; 4215 mWebViewCore.sendMessage(WebViewCore.EventHub.SCROLL_LAYER, mCurrentScrollingLayerId, 4216 mScrollingLayerRect); 4217 onScrollChanged(mScrollX, mScrollY, mScrollX, mScrollY); 4218 invalidate(); 4219 } 4220 4221 private static int computeDuration(int dx, int dy) { 4222 int distance = Math.max(Math.abs(dx), Math.abs(dy)); 4223 int duration = distance * 1000 / STD_SPEED; 4224 return Math.min(duration, MAX_DURATION); 4225 } 4226 4227 // helper to pin the scrollBy parameters (already in view coordinates) 4228 // returns true if the scroll was changed 4229 private boolean pinScrollBy(int dx, int dy, boolean animate, int animationDuration) { 4230 return pinScrollTo(mScrollX + dx, mScrollY + dy, animate, animationDuration); 4231 } 4232 // helper to pin the scrollTo parameters (already in view coordinates) 4233 // returns true if the scroll was changed 4234 private boolean pinScrollTo(int x, int y, boolean animate, int animationDuration) { 4235 x = pinLocX(x); 4236 y = pinLocY(y); 4237 int dx = x - mScrollX; 4238 int dy = y - mScrollY; 4239 4240 if ((dx | dy) == 0) { 4241 return false; 4242 } 4243 abortAnimation(); 4244 if (animate) { 4245 // Log.d(LOGTAG, "startScroll: " + dx + " " + dy); 4246 mScroller.startScroll(mScrollX, mScrollY, dx, dy, 4247 animationDuration > 0 ? animationDuration : computeDuration(dx, dy)); 4248 awakenScrollBars(mScroller.getDuration()); 4249 invalidate(); 4250 } else { 4251 scrollTo(x, y); 4252 } 4253 return true; 4254 } 4255 4256 // Scale from content to view coordinates, and pin. 4257 // Also called by jni webview.cpp 4258 private boolean setContentScrollBy(int cx, int cy, boolean animate) { 4259 if (mDrawHistory) { 4260 // disallow WebView to change the scroll position as History Picture 4261 // is used in the view system. 4262 // TODO: as we switchOutDrawHistory when trackball or navigation 4263 // keys are hit, this should be safe. Right? 4264 return false; 4265 } 4266 cx = contentToViewDimension(cx); 4267 cy = contentToViewDimension(cy); 4268 if (mHeightCanMeasure) { 4269 // move our visible rect according to scroll request 4270 if (cy != 0) { 4271 Rect tempRect = new Rect(); 4272 calcOurVisibleRect(tempRect); 4273 tempRect.offset(cx, cy); 4274 requestRectangleOnScreen(tempRect); 4275 } 4276 // FIXME: We scroll horizontally no matter what because currently 4277 // ScrollView and ListView will not scroll horizontally. 4278 // FIXME: Why do we only scroll horizontally if there is no 4279 // vertical scroll? 4280// Log.d(LOGTAG, "setContentScrollBy cy=" + cy); 4281 return cy == 0 && cx != 0 && pinScrollBy(cx, 0, animate, 0); 4282 } else { 4283 return pinScrollBy(cx, cy, animate, 0); 4284 } 4285 } 4286 4287 /** 4288 * Called by CallbackProxy when the page starts loading. 4289 * @param url The URL of the page which has started loading. 4290 */ 4291 /* package */ void onPageStarted(String url) { 4292 // every time we start a new page, we want to reset the 4293 // WebView certificate: if the new site is secure, we 4294 // will reload it and get a new certificate set; 4295 // if the new site is not secure, the certificate must be 4296 // null, and that will be the case 4297 setCertificate(null); 4298 4299 // reset the flag since we set to true in if need after 4300 // loading is see onPageFinished(Url) 4301 mAccessibilityScriptInjected = false; 4302 } 4303 4304 /** 4305 * Called by CallbackProxy when the page finishes loading. 4306 * @param url The URL of the page which has finished loading. 4307 */ 4308 /* package */ void onPageFinished(String url) { 4309 if (mPageThatNeedsToSlideTitleBarOffScreen != null) { 4310 // If the user is now on a different page, or has scrolled the page 4311 // past the point where the title bar is offscreen, ignore the 4312 // scroll request. 4313 if (mPageThatNeedsToSlideTitleBarOffScreen.equals(url) 4314 && mScrollX == 0 && mScrollY == 0) { 4315 pinScrollTo(0, mYDistanceToSlideTitleOffScreen, true, 4316 SLIDE_TITLE_DURATION); 4317 } 4318 mPageThatNeedsToSlideTitleBarOffScreen = null; 4319 } 4320 mZoomManager.onPageFinished(url); 4321 injectAccessibilityForUrl(url); 4322 } 4323 4324 /** 4325 * This method injects accessibility in the loaded document if accessibility 4326 * is enabled. If JavaScript is enabled we try to inject a URL specific script. 4327 * If no URL specific script is found or JavaScript is disabled we fallback to 4328 * the default {@link AccessibilityInjector} implementation. 4329 * </p> 4330 * If the URL has the "axs" paramter set to 1 it has already done the 4331 * script injection so we do nothing. If the parameter is set to 0 4332 * the URL opts out accessibility script injection so we fall back to 4333 * the default {@link AccessibilityInjector}. 4334 * </p> 4335 * Note: If the user has not opted-in the accessibility script injection no scripts 4336 * are injected rather the default {@link AccessibilityInjector} implementation 4337 * is used. 4338 * 4339 * @param url The URL loaded by this {@link WebView}. 4340 */ 4341 private void injectAccessibilityForUrl(String url) { 4342 if (mWebViewCore == null) { 4343 return; 4344 } 4345 AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext); 4346 4347 if (!accessibilityManager.isEnabled()) { 4348 // it is possible that accessibility was turned off between reloads 4349 ensureAccessibilityScriptInjectorInstance(false); 4350 return; 4351 } 4352 4353 if (!getSettings().getJavaScriptEnabled()) { 4354 // no JS so we fallback to the basic buil-in support 4355 ensureAccessibilityScriptInjectorInstance(true); 4356 return; 4357 } 4358 4359 // check the URL "axs" parameter to choose appropriate action 4360 int axsParameterValue = getAxsUrlParameterValue(url); 4361 if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED) { 4362 boolean onDeviceScriptInjectionEnabled = (Settings.Secure.getInt(mContext 4363 .getContentResolver(), Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, 0) == 1); 4364 if (onDeviceScriptInjectionEnabled) { 4365 ensureAccessibilityScriptInjectorInstance(false); 4366 // neither script injected nor script injection opted out => we inject 4367 loadUrl(getScreenReaderInjectingJs()); 4368 // TODO: Set this flag after successfull script injection. Maybe upon injection 4369 // the chooser should update the meta tag and we check it to declare success 4370 mAccessibilityScriptInjected = true; 4371 } else { 4372 // injection disabled so we fallback to the basic built-in support 4373 ensureAccessibilityScriptInjectorInstance(true); 4374 } 4375 } else if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT) { 4376 // injection opted out so we fallback to the basic buil-in support 4377 ensureAccessibilityScriptInjectorInstance(true); 4378 } else if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED) { 4379 ensureAccessibilityScriptInjectorInstance(false); 4380 // the URL provides accessibility but we still need to add our generic script 4381 loadUrl(getScreenReaderInjectingJs()); 4382 } else { 4383 Log.e(LOGTAG, "Unknown URL value for the \"axs\" URL parameter: " + axsParameterValue); 4384 } 4385 } 4386 4387 /** 4388 * Ensures the instance of the {@link AccessibilityInjector} to be present ot not. 4389 * 4390 * @param present True to ensure an insance, false to ensure no instance. 4391 */ 4392 private void ensureAccessibilityScriptInjectorInstance(boolean present) { 4393 if (present) { 4394 if (mAccessibilityInjector == null) { 4395 mAccessibilityInjector = new AccessibilityInjector(this); 4396 } 4397 } else { 4398 mAccessibilityInjector = null; 4399 } 4400 } 4401 4402 /** 4403 * Gets JavaScript that injects a screen-reader. 4404 * 4405 * @return The JavaScript snippet. 4406 */ 4407 private String getScreenReaderInjectingJs() { 4408 String screenReaderUrl = Settings.Secure.getString(mContext.getContentResolver(), 4409 Settings.Secure.ACCESSIBILITY_SCREEN_READER_URL); 4410 return String.format(ACCESSIBILITY_SCREEN_READER_JAVASCRIPT_TEMPLATE, screenReaderUrl); 4411 } 4412 4413 /** 4414 * Gets the "axs" URL parameter value. 4415 * 4416 * @param url A url to fetch the paramter from. 4417 * @return The parameter value if such, -1 otherwise. 4418 */ 4419 private int getAxsUrlParameterValue(String url) { 4420 if (mMatchAxsUrlParameterPattern == null) { 4421 mMatchAxsUrlParameterPattern = Pattern.compile(PATTERN_MATCH_AXS_URL_PARAMETER); 4422 } 4423 Matcher matcher = mMatchAxsUrlParameterPattern.matcher(url); 4424 if (matcher.find()) { 4425 String keyValuePair = url.substring(matcher.start(), matcher.end()); 4426 return Integer.parseInt(keyValuePair.split("=")[1]); 4427 } 4428 return -1; 4429 } 4430 4431 /** 4432 * The URL of a page that sent a message to scroll the title bar off screen. 4433 * 4434 * Many mobile sites tell the page to scroll to (0,1) in order to scroll the 4435 * title bar off the screen. Sometimes, the scroll position is set before 4436 * the page finishes loading. Rather than scrolling while the page is still 4437 * loading, keep track of the URL and new scroll position so we can perform 4438 * the scroll once the page finishes loading. 4439 */ 4440 private String mPageThatNeedsToSlideTitleBarOffScreen; 4441 4442 /** 4443 * The destination Y scroll position to be used when the page finishes 4444 * loading. See mPageThatNeedsToSlideTitleBarOffScreen. 4445 */ 4446 private int mYDistanceToSlideTitleOffScreen; 4447 4448 // scale from content to view coordinates, and pin 4449 // return true if pin caused the final x/y different than the request cx/cy, 4450 // and a future scroll may reach the request cx/cy after our size has 4451 // changed 4452 // return false if the view scroll to the exact position as it is requested, 4453 // where negative numbers are taken to mean 0 4454 private boolean setContentScrollTo(int cx, int cy) { 4455 if (mDrawHistory) { 4456 // disallow WebView to change the scroll position as History Picture 4457 // is used in the view system. 4458 // One known case where this is called is that WebCore tries to 4459 // restore the scroll position. As history Picture already uses the 4460 // saved scroll position, it is ok to skip this. 4461 return false; 4462 } 4463 int vx; 4464 int vy; 4465 if ((cx | cy) == 0) { 4466 // If the page is being scrolled to (0,0), do not add in the title 4467 // bar's height, and simply scroll to (0,0). (The only other work 4468 // in contentToView_ is to multiply, so this would not change 0.) 4469 vx = 0; 4470 vy = 0; 4471 } else { 4472 vx = contentToViewX(cx); 4473 vy = contentToViewY(cy); 4474 } 4475// Log.d(LOGTAG, "content scrollTo [" + cx + " " + cy + "] view=[" + 4476// vx + " " + vy + "]"); 4477 // Some mobile sites attempt to scroll the title bar off the page by 4478 // scrolling to (0,1). If we are at the top left corner of the 4479 // page, assume this is an attempt to scroll off the title bar, and 4480 // animate the title bar off screen slowly enough that the user can see 4481 // it. 4482 if (cx == 0 && cy == 1 && mScrollX == 0 && mScrollY == 0 4483 && mTitleBar != null) { 4484 // FIXME: 100 should be defined somewhere as our max progress. 4485 if (getProgress() < 100) { 4486 // Wait to scroll the title bar off screen until the page has 4487 // finished loading. Keep track of the URL and the destination 4488 // Y position 4489 mPageThatNeedsToSlideTitleBarOffScreen = getUrl(); 4490 mYDistanceToSlideTitleOffScreen = vy; 4491 } else { 4492 pinScrollTo(vx, vy, true, SLIDE_TITLE_DURATION); 4493 } 4494 // Since we are animating, we have not yet reached the desired 4495 // scroll position. Do not return true to request another attempt 4496 return false; 4497 } 4498 pinScrollTo(vx, vy, false, 0); 4499 // If the request was to scroll to a negative coordinate, treat it as if 4500 // it was a request to scroll to 0 4501 if ((mScrollX != vx && cx >= 0) || (mScrollY != vy && cy >= 0)) { 4502 return true; 4503 } else { 4504 return false; 4505 } 4506 } 4507 4508 // scale from content to view coordinates, and pin 4509 private void spawnContentScrollTo(int cx, int cy) { 4510 if (mDrawHistory) { 4511 // disallow WebView to change the scroll position as History Picture 4512 // is used in the view system. 4513 return; 4514 } 4515 int vx = contentToViewX(cx); 4516 int vy = contentToViewY(cy); 4517 pinScrollTo(vx, vy, true, 0); 4518 } 4519 4520 /** 4521 * These are from webkit, and are in content coordinate system (unzoomed) 4522 */ 4523 private void contentSizeChanged(boolean updateLayout) { 4524 // suppress 0,0 since we usually see real dimensions soon after 4525 // this avoids drawing the prev content in a funny place. If we find a 4526 // way to consolidate these notifications, this check may become 4527 // obsolete 4528 if ((mContentWidth | mContentHeight) == 0) { 4529 return; 4530 } 4531 4532 if (mHeightCanMeasure) { 4533 if (getMeasuredHeight() != contentToViewDimension(mContentHeight) 4534 || updateLayout) { 4535 requestLayout(); 4536 } 4537 } else if (mWidthCanMeasure) { 4538 if (getMeasuredWidth() != contentToViewDimension(mContentWidth) 4539 || updateLayout) { 4540 requestLayout(); 4541 } 4542 } else { 4543 // If we don't request a layout, try to send our view size to the 4544 // native side to ensure that WebCore has the correct dimensions. 4545 sendViewSizeZoom(false); 4546 } 4547 } 4548 4549 /** 4550 * Set the WebViewClient that will receive various notifications and 4551 * requests. This will replace the current handler. 4552 * @param client An implementation of WebViewClient. 4553 */ 4554 public void setWebViewClient(WebViewClient client) { 4555 checkThread(); 4556 mCallbackProxy.setWebViewClient(client); 4557 } 4558 4559 /** 4560 * Gets the WebViewClient 4561 * @return the current WebViewClient instance. 4562 * 4563 * @hide This is an implementation detail. 4564 */ 4565 public WebViewClient getWebViewClient() { 4566 return mCallbackProxy.getWebViewClient(); 4567 } 4568 4569 /** 4570 * Register the interface to be used when content can not be handled by 4571 * the rendering engine, and should be downloaded instead. This will replace 4572 * the current handler. 4573 * @param listener An implementation of DownloadListener. 4574 */ 4575 public void setDownloadListener(DownloadListener listener) { 4576 checkThread(); 4577 mCallbackProxy.setDownloadListener(listener); 4578 } 4579 4580 /** 4581 * Set the chrome handler. This is an implementation of WebChromeClient for 4582 * use in handling JavaScript dialogs, favicons, titles, and the progress. 4583 * This will replace the current handler. 4584 * @param client An implementation of WebChromeClient. 4585 */ 4586 public void setWebChromeClient(WebChromeClient client) { 4587 checkThread(); 4588 mCallbackProxy.setWebChromeClient(client); 4589 } 4590 4591 /** 4592 * Gets the chrome handler. 4593 * @return the current WebChromeClient instance. 4594 * 4595 * @hide This is an implementation detail. 4596 */ 4597 public WebChromeClient getWebChromeClient() { 4598 return mCallbackProxy.getWebChromeClient(); 4599 } 4600 4601 /** 4602 * Set the back/forward list client. This is an implementation of 4603 * WebBackForwardListClient for handling new items and changes in the 4604 * history index. 4605 * @param client An implementation of WebBackForwardListClient. 4606 * {@hide} 4607 */ 4608 public void setWebBackForwardListClient(WebBackForwardListClient client) { 4609 mCallbackProxy.setWebBackForwardListClient(client); 4610 } 4611 4612 /** 4613 * Gets the WebBackForwardListClient. 4614 * {@hide} 4615 */ 4616 public WebBackForwardListClient getWebBackForwardListClient() { 4617 return mCallbackProxy.getWebBackForwardListClient(); 4618 } 4619 4620 /** 4621 * Set the Picture listener. This is an interface used to receive 4622 * notifications of a new Picture. 4623 * @param listener An implementation of WebView.PictureListener. 4624 * @deprecated This method is now obsolete. 4625 */ 4626 @Deprecated 4627 public void setPictureListener(PictureListener listener) { 4628 checkThread(); 4629 mPictureListener = listener; 4630 } 4631 4632 /** 4633 * {@hide} 4634 */ 4635 /* FIXME: Debug only! Remove for SDK! */ 4636 public void externalRepresentation(Message callback) { 4637 mWebViewCore.sendMessage(EventHub.REQUEST_EXT_REPRESENTATION, callback); 4638 } 4639 4640 /** 4641 * {@hide} 4642 */ 4643 /* FIXME: Debug only! Remove for SDK! */ 4644 public void documentAsText(Message callback) { 4645 mWebViewCore.sendMessage(EventHub.REQUEST_DOC_AS_TEXT, callback); 4646 } 4647 4648 /** 4649 * This method injects the supplied Java object into the WebView. The 4650 * object is injected into the JavaScript context of the main frame, using 4651 * the supplied name. This allows the Java object to be accessed from 4652 * JavaScript. Note that that injected objects will not appear in 4653 * JavaScript until the page is next (re)loaded. For example: 4654 * <pre> webView.addJavascriptInterface(new Object(), "injectedObject"); 4655 * webView.loadData("<!DOCTYPE html><title></title>", "text/html", null); 4656 * webView.loadUrl("javascript:alert(injectedObject.toString())");</pre> 4657 * <p><strong>IMPORTANT:</strong> 4658 * <ul> 4659 * <li> addJavascriptInterface() can be used to allow JavaScript to control 4660 * the host application. This is a powerful feature, but also presents a 4661 * security risk. Use of this method in a WebView containing untrusted 4662 * content could allow an attacker to manipulate the host application in 4663 * unintended ways, executing Java code with the permissions of the host 4664 * application. Use extreme care when using this method in a WebView which 4665 * could contain untrusted content. 4666 * <li> JavaScript interacts with Java object on a private, background 4667 * thread of the WebView. Care is therefore required to maintain thread 4668 * safety.</li> 4669 * </ul></p> 4670 * @param object The Java object to inject into the WebView's JavaScript 4671 * context. Null values are ignored. 4672 * @param name The name used to expose the instance in JavaScript. 4673 */ 4674 public void addJavascriptInterface(Object object, String name) { 4675 checkThread(); 4676 if (object == null) { 4677 return; 4678 } 4679 WebViewCore.JSInterfaceData arg = new WebViewCore.JSInterfaceData(); 4680 arg.mObject = object; 4681 arg.mInterfaceName = name; 4682 mWebViewCore.sendMessage(EventHub.ADD_JS_INTERFACE, arg); 4683 } 4684 4685 /** 4686 * Removes a previously added JavaScript interface with the given name. 4687 * @param interfaceName The name of the interface to remove. 4688 */ 4689 public void removeJavascriptInterface(String interfaceName) { 4690 checkThread(); 4691 if (mWebViewCore != null) { 4692 WebViewCore.JSInterfaceData arg = new WebViewCore.JSInterfaceData(); 4693 arg.mInterfaceName = interfaceName; 4694 mWebViewCore.sendMessage(EventHub.REMOVE_JS_INTERFACE, arg); 4695 } 4696 } 4697 4698 /** 4699 * Return the WebSettings object used to control the settings for this 4700 * WebView. 4701 * @return A WebSettings object that can be used to control this WebView's 4702 * settings. 4703 */ 4704 public WebSettings getSettings() { 4705 checkThread(); 4706 return (mWebViewCore != null) ? mWebViewCore.getSettings() : null; 4707 } 4708 4709 /** 4710 * Return the list of currently loaded plugins. 4711 * @return The list of currently loaded plugins. 4712 * 4713 * @hide 4714 * @deprecated This was used for Gears, which has been deprecated. 4715 */ 4716 @Deprecated 4717 public static synchronized PluginList getPluginList() { 4718 checkThread(); 4719 return new PluginList(); 4720 } 4721 4722 /** 4723 * @hide 4724 * @deprecated This was used for Gears, which has been deprecated. 4725 */ 4726 @Deprecated 4727 public void refreshPlugins(boolean reloadOpenPages) { 4728 checkThread(); 4729 } 4730 4731 //------------------------------------------------------------------------- 4732 // Override View methods 4733 //------------------------------------------------------------------------- 4734 4735 @Override 4736 protected void finalize() throws Throwable { 4737 try { 4738 if (mNativeClass != 0) { 4739 mPrivateHandler.post(new Runnable() { 4740 @Override 4741 public void run() { 4742 destroy(); 4743 } 4744 }); 4745 } 4746 } finally { 4747 super.finalize(); 4748 } 4749 } 4750 4751 @Override 4752 protected boolean drawChild(Canvas canvas, View child, long drawingTime) { 4753 if (child == mTitleBar) { 4754 // When drawing the title bar, move it horizontally to always show 4755 // at the top of the WebView. 4756 mTitleBar.offsetLeftAndRight(mScrollX - mTitleBar.getLeft()); 4757 int newTop = 0; 4758 if (mTitleGravity == Gravity.NO_GRAVITY) { 4759 newTop = Math.min(0, mScrollY); 4760 } else if (mTitleGravity == Gravity.TOP) { 4761 newTop = mScrollY; 4762 } 4763 mTitleBar.setBottom(newTop + mTitleBar.getHeight()); 4764 mTitleBar.setTop(newTop); 4765 } 4766 return super.drawChild(canvas, child, drawingTime); 4767 } 4768 4769 private void drawContent(Canvas canvas, boolean drawRings) { 4770 drawCoreAndCursorRing(canvas, mBackgroundColor, 4771 mDrawCursorRing && drawRings); 4772 } 4773 4774 /** 4775 * Draw the background when beyond bounds 4776 * @param canvas Canvas to draw into 4777 */ 4778 private void drawOverScrollBackground(Canvas canvas) { 4779 if (mOverScrollBackground == null) { 4780 mOverScrollBackground = new Paint(); 4781 Bitmap bm = BitmapFactory.decodeResource( 4782 mContext.getResources(), 4783 com.android.internal.R.drawable.status_bar_background); 4784 mOverScrollBackground.setShader(new BitmapShader(bm, 4785 Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)); 4786 mOverScrollBorder = new Paint(); 4787 mOverScrollBorder.setStyle(Paint.Style.STROKE); 4788 mOverScrollBorder.setStrokeWidth(0); 4789 mOverScrollBorder.setColor(0xffbbbbbb); 4790 } 4791 4792 int top = 0; 4793 int right = computeRealHorizontalScrollRange(); 4794 int bottom = top + computeRealVerticalScrollRange(); 4795 // first draw the background and anchor to the top of the view 4796 canvas.save(); 4797 canvas.translate(mScrollX, mScrollY); 4798 canvas.clipRect(-mScrollX, top - mScrollY, right - mScrollX, bottom 4799 - mScrollY, Region.Op.DIFFERENCE); 4800 canvas.drawPaint(mOverScrollBackground); 4801 canvas.restore(); 4802 // then draw the border 4803 canvas.drawRect(-1, top - 1, right, bottom, mOverScrollBorder); 4804 // next clip the region for the content 4805 canvas.clipRect(0, top, right, bottom); 4806 } 4807 4808 @Override 4809 protected void onDraw(Canvas canvas) { 4810 if (inFullScreenMode()) { 4811 return; // no need to draw anything if we aren't visible. 4812 } 4813 // if mNativeClass is 0, the WebView is either destroyed or not 4814 // initialized. In either case, just draw the background color and return 4815 if (mNativeClass == 0) { 4816 canvas.drawColor(mBackgroundColor); 4817 return; 4818 } 4819 4820 // if both mContentWidth and mContentHeight are 0, it means there is no 4821 // valid Picture passed to WebView yet. This can happen when WebView 4822 // just starts. Draw the background and return. 4823 if ((mContentWidth | mContentHeight) == 0 && mHistoryPicture == null) { 4824 canvas.drawColor(mBackgroundColor); 4825 return; 4826 } 4827 4828 if (canvas.isHardwareAccelerated()) { 4829 mZoomManager.setHardwareAccelerated(); 4830 } else { 4831 mWebViewCore.resumeWebKitDraw(); 4832 } 4833 4834 int saveCount = canvas.save(); 4835 if (mInOverScrollMode && !getSettings() 4836 .getUseWebViewBackgroundForOverscrollBackground()) { 4837 drawOverScrollBackground(canvas); 4838 } 4839 if (mTitleBar != null) { 4840 canvas.translate(0, getTitleHeight()); 4841 } 4842 boolean drawNativeRings = !sDisableNavcache; 4843 drawContent(canvas, drawNativeRings); 4844 canvas.restoreToCount(saveCount); 4845 4846 if (AUTO_REDRAW_HACK && mAutoRedraw) { 4847 invalidate(); 4848 } 4849 mWebViewCore.signalRepaintDone(); 4850 4851 if (mOverScrollGlow != null && mOverScrollGlow.drawEdgeGlows(canvas)) { 4852 invalidate(); 4853 } 4854 4855 if (mFocusTransition != null) { 4856 mFocusTransition.draw(canvas); 4857 } else if (shouldDrawHighlightRect()) { 4858 RegionIterator iter = new RegionIterator(mTouchHighlightRegion); 4859 Rect r = new Rect(); 4860 while (iter.next(r)) { 4861 canvas.drawRect(r, mTouchHightlightPaint); 4862 } 4863 } 4864 if (DEBUG_TOUCH_HIGHLIGHT) { 4865 if (getSettings().getNavDump()) { 4866 if ((mTouchHighlightX | mTouchHighlightY) != 0) { 4867 if (mTouchCrossHairColor == null) { 4868 mTouchCrossHairColor = new Paint(); 4869 mTouchCrossHairColor.setColor(Color.RED); 4870 } 4871 canvas.drawLine(mTouchHighlightX - mNavSlop, 4872 mTouchHighlightY - mNavSlop, mTouchHighlightX 4873 + mNavSlop + 1, mTouchHighlightY + mNavSlop 4874 + 1, mTouchCrossHairColor); 4875 canvas.drawLine(mTouchHighlightX + mNavSlop + 1, 4876 mTouchHighlightY - mNavSlop, mTouchHighlightX 4877 - mNavSlop, 4878 mTouchHighlightY + mNavSlop + 1, 4879 mTouchCrossHairColor); 4880 } 4881 } 4882 } 4883 } 4884 4885 private void removeTouchHighlight() { 4886 mWebViewCore.removeMessages(EventHub.HIT_TEST); 4887 mPrivateHandler.removeMessages(HIT_TEST_RESULT); 4888 setTouchHighlightRects(null); 4889 } 4890 4891 @Override 4892 public void setLayoutParams(ViewGroup.LayoutParams params) { 4893 if (params.height == LayoutParams.WRAP_CONTENT) { 4894 mWrapContent = true; 4895 } 4896 super.setLayoutParams(params); 4897 } 4898 4899 @Override 4900 public boolean performLongClick() { 4901 // performLongClick() is the result of a delayed message. If we switch 4902 // to windows overview, the WebView will be temporarily removed from the 4903 // view system. In that case, do nothing. 4904 if (getParent() == null) return false; 4905 4906 // A multi-finger gesture can look like a long press; make sure we don't take 4907 // long press actions if we're scaling. 4908 final ScaleGestureDetector detector = mZoomManager.getMultiTouchGestureDetector(); 4909 if (detector != null && detector.isInProgress()) { 4910 return false; 4911 } 4912 4913 if (mNativeClass != 0 && nativeCursorIsTextInput()) { 4914 // Send the click so that the textfield is in focus 4915 centerKeyPressOnTextField(); 4916 rebuildWebTextView(); 4917 } else { 4918 clearTextEntry(); 4919 } 4920 if (inEditingMode()) { 4921 // Since we just called rebuildWebTextView, the layout is not set 4922 // properly. Update it so it can correctly find the word to select. 4923 mWebTextView.ensureLayout(); 4924 // Provide a touch down event to WebTextView, which will allow it 4925 // to store the location to use in performLongClick. 4926 AbsoluteLayout.LayoutParams params 4927 = (AbsoluteLayout.LayoutParams) mWebTextView.getLayoutParams(); 4928 MotionEvent fake = MotionEvent.obtain(mLastTouchTime, 4929 mLastTouchTime, MotionEvent.ACTION_DOWN, 4930 mLastTouchX - params.x + mScrollX, 4931 mLastTouchY - params.y + mScrollY, 0); 4932 mWebTextView.dispatchTouchEvent(fake); 4933 return mWebTextView.performLongClick(); 4934 } 4935 if (mSelectingText) return false; // long click does nothing on selection 4936 /* if long click brings up a context menu, the super function 4937 * returns true and we're done. Otherwise, nothing happened when 4938 * the user clicked. */ 4939 if (super.performLongClick()) { 4940 return true; 4941 } 4942 /* In the case where the application hasn't already handled the long 4943 * click action, look for a word under the click. If one is found, 4944 * animate the text selection into view. 4945 * FIXME: no animation code yet */ 4946 final boolean isSelecting = selectText(); 4947 if (isSelecting) { 4948 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 4949 } else if (focusCandidateIsEditableText()) { 4950 mSelectCallback = new SelectActionModeCallback(); 4951 mSelectCallback.setWebView(this); 4952 mSelectCallback.setTextSelected(false); 4953 startActionMode(mSelectCallback); 4954 } 4955 return isSelecting; 4956 } 4957 4958 /** 4959 * Select the word at the last click point. 4960 * 4961 * @hide This is an implementation detail. 4962 */ 4963 public boolean selectText() { 4964 int x = viewToContentX(mLastTouchX + mScrollX); 4965 int y = viewToContentY(mLastTouchY + mScrollY); 4966 return selectText(x, y); 4967 } 4968 4969 /** 4970 * Select the word at the indicated content coordinates. 4971 */ 4972 boolean selectText(int x, int y) { 4973 mWebViewCore.sendMessage(EventHub.SELECT_WORD_AT, x, y); 4974 return true; 4975 } 4976 4977 private int mOrientation = Configuration.ORIENTATION_UNDEFINED; 4978 4979 @Override 4980 protected void onConfigurationChanged(Configuration newConfig) { 4981 mCachedOverlappingActionModeHeight = -1; 4982 if (mSelectingText && mOrientation != newConfig.orientation) { 4983 selectionDone(); 4984 } 4985 mOrientation = newConfig.orientation; 4986 if (mWebViewCore != null && !mBlockWebkitViewMessages) { 4987 mWebViewCore.sendMessage(EventHub.CLEAR_CONTENT); 4988 } 4989 } 4990 4991 /** 4992 * Keep track of the Callback so we can end its ActionMode or remove its 4993 * titlebar. 4994 */ 4995 private SelectActionModeCallback mSelectCallback; 4996 4997 // These values are possible options for didUpdateWebTextViewDimensions. 4998 private static final int FULLY_ON_SCREEN = 0; 4999 private static final int INTERSECTS_SCREEN = 1; 5000 private static final int ANYWHERE = 2; 5001 5002 /** 5003 * Check to see if the focused textfield/textarea is still on screen. If it 5004 * is, update the the dimensions and location of WebTextView. Otherwise, 5005 * remove the WebTextView. Should be called when the zoom level changes. 5006 * @param intersection How to determine whether the textfield/textarea is 5007 * still on screen. 5008 * @return boolean True if the textfield/textarea is still on screen and the 5009 * dimensions/location of WebTextView have been updated. 5010 */ 5011 private boolean didUpdateWebTextViewDimensions(int intersection) { 5012 Rect contentBounds = nativeFocusCandidateNodeBounds(); 5013 Rect vBox = contentToViewRect(contentBounds); 5014 Rect visibleRect = new Rect(); 5015 calcOurVisibleRect(visibleRect); 5016 offsetByLayerScrollPosition(vBox); 5017 // If the textfield is on screen, place the WebTextView in 5018 // its new place, accounting for our new scroll/zoom values, 5019 // and adjust its textsize. 5020 boolean onScreen; 5021 switch (intersection) { 5022 case FULLY_ON_SCREEN: 5023 onScreen = visibleRect.contains(vBox); 5024 break; 5025 case INTERSECTS_SCREEN: 5026 onScreen = Rect.intersects(visibleRect, vBox); 5027 break; 5028 case ANYWHERE: 5029 onScreen = true; 5030 break; 5031 default: 5032 throw new AssertionError( 5033 "invalid parameter passed to didUpdateWebTextViewDimensions"); 5034 } 5035 if (onScreen) { 5036 mWebTextView.setRect(vBox.left, vBox.top, vBox.width(), 5037 vBox.height()); 5038 mWebTextView.updateTextSize(); 5039 updateWebTextViewPadding(); 5040 return true; 5041 } else { 5042 // The textfield is now off screen. The user probably 5043 // was not zooming to see the textfield better. Remove 5044 // the WebTextView. If the user types a key, and the 5045 // textfield is still in focus, we will reconstruct 5046 // the WebTextView and scroll it back on screen. 5047 mWebTextView.remove(); 5048 return false; 5049 } 5050 } 5051 5052 private void offsetByLayerScrollPosition(Rect box) { 5053 if ((mCurrentScrollingLayerId != 0) 5054 && (mCurrentScrollingLayerId == nativeFocusCandidateLayerId())) { 5055 box.offsetTo(box.left - mScrollingLayerRect.left, 5056 box.top - mScrollingLayerRect.top); 5057 } 5058 } 5059 5060 void setBaseLayer(int layer, Region invalRegion, boolean showVisualIndicator, 5061 boolean isPictureAfterFirstLayout) { 5062 if (mNativeClass == 0) 5063 return; 5064 boolean queueFull; 5065 queueFull = nativeSetBaseLayer(mNativeClass, layer, invalRegion, 5066 showVisualIndicator, isPictureAfterFirstLayout); 5067 5068 if (layer == 0 || isPictureAfterFirstLayout) { 5069 mWebViewCore.resumeWebKitDraw(); 5070 } else if (queueFull) { 5071 // temporarily disable webkit draw throttling 5072 // TODO: re-enable 5073 // mWebViewCore.pauseWebKitDraw(); 5074 } 5075 5076 if (mHTML5VideoViewProxy != null) { 5077 mHTML5VideoViewProxy.setBaseLayer(layer); 5078 } 5079 } 5080 5081 int getBaseLayer() { 5082 if (mNativeClass == 0) { 5083 return 0; 5084 } 5085 return nativeGetBaseLayer(); 5086 } 5087 5088 private void onZoomAnimationStart() { 5089 // If it is in password mode, turn it off so it does not draw misplaced. 5090 if (inEditingMode()) { 5091 mWebTextView.setVisibility(INVISIBLE); 5092 } 5093 } 5094 5095 private void onZoomAnimationEnd() { 5096 // adjust the edit text view if needed 5097 if (inEditingMode() 5098 && didUpdateWebTextViewDimensions(FULLY_ON_SCREEN)) { 5099 // If it is a password field, start drawing the WebTextView once 5100 // again. 5101 mWebTextView.setVisibility(VISIBLE); 5102 } 5103 } 5104 5105 void onFixedLengthZoomAnimationStart() { 5106 WebViewCore.pauseUpdatePicture(getWebViewCore()); 5107 onZoomAnimationStart(); 5108 } 5109 5110 void onFixedLengthZoomAnimationEnd() { 5111 if (!mBlockWebkitViewMessages && !mSelectingText) { 5112 WebViewCore.resumeUpdatePicture(mWebViewCore); 5113 } 5114 onZoomAnimationEnd(); 5115 } 5116 5117 private static final int ZOOM_BITS = Paint.FILTER_BITMAP_FLAG | 5118 Paint.DITHER_FLAG | 5119 Paint.SUBPIXEL_TEXT_FLAG; 5120 private static final int SCROLL_BITS = Paint.FILTER_BITMAP_FLAG | 5121 Paint.DITHER_FLAG; 5122 5123 private final DrawFilter mZoomFilter = 5124 new PaintFlagsDrawFilter(ZOOM_BITS, Paint.LINEAR_TEXT_FLAG); 5125 // If we need to trade better quality for speed, set mScrollFilter to null 5126 private final DrawFilter mScrollFilter = 5127 new PaintFlagsDrawFilter(SCROLL_BITS, 0); 5128 5129 private void drawCoreAndCursorRing(Canvas canvas, int color, 5130 boolean drawCursorRing) { 5131 if (mDrawHistory) { 5132 canvas.scale(mZoomManager.getScale(), mZoomManager.getScale()); 5133 canvas.drawPicture(mHistoryPicture); 5134 return; 5135 } 5136 if (mNativeClass == 0) return; 5137 5138 boolean animateZoom = mZoomManager.isFixedLengthAnimationInProgress(); 5139 boolean animateScroll = ((!mScroller.isFinished() 5140 || mVelocityTracker != null) 5141 && (mTouchMode != TOUCH_DRAG_MODE || 5142 mHeldMotionless != MOTIONLESS_TRUE)) 5143 || mDeferTouchMode == TOUCH_DRAG_MODE; 5144 if (mTouchMode == TOUCH_DRAG_MODE) { 5145 if (mHeldMotionless == MOTIONLESS_PENDING) { 5146 mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS); 5147 mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS); 5148 mHeldMotionless = MOTIONLESS_FALSE; 5149 } 5150 if (mHeldMotionless == MOTIONLESS_FALSE) { 5151 mPrivateHandler.sendMessageDelayed(mPrivateHandler 5152 .obtainMessage(DRAG_HELD_MOTIONLESS), MOTIONLESS_TIME); 5153 mPrivateHandler.sendMessageDelayed(mPrivateHandler 5154 .obtainMessage(AWAKEN_SCROLL_BARS), 5155 ViewConfiguration.getScrollDefaultDelay()); 5156 mHeldMotionless = MOTIONLESS_PENDING; 5157 } 5158 } 5159 int saveCount = canvas.save(); 5160 if (animateZoom) { 5161 mZoomManager.animateZoom(canvas); 5162 } else if (!canvas.isHardwareAccelerated()) { 5163 canvas.scale(mZoomManager.getScale(), mZoomManager.getScale()); 5164 } 5165 5166 boolean UIAnimationsRunning = false; 5167 // Currently for each draw we compute the animation values; 5168 // We may in the future decide to do that independently. 5169 if (mNativeClass != 0 && !canvas.isHardwareAccelerated() 5170 && nativeEvaluateLayersAnimations(mNativeClass)) { 5171 UIAnimationsRunning = true; 5172 // If we have unfinished (or unstarted) animations, 5173 // we ask for a repaint. We only need to do this in software 5174 // rendering (with hardware rendering we already have a different 5175 // method of requesting a repaint) 5176 mWebViewCore.sendMessage(EventHub.NOTIFY_ANIMATION_STARTED); 5177 invalidate(); 5178 } 5179 5180 // decide which adornments to draw 5181 int extras = DRAW_EXTRAS_NONE; 5182 if (!mFindIsUp) { 5183 if (mSelectingText) { 5184 extras = DRAW_EXTRAS_SELECTION; 5185 } else if (drawCursorRing) { 5186 extras = DRAW_EXTRAS_CURSOR_RING; 5187 } 5188 } 5189 if (DebugFlags.WEB_VIEW) { 5190 Log.v(LOGTAG, "mFindIsUp=" + mFindIsUp 5191 + " mSelectingText=" + mSelectingText 5192 + " nativePageShouldHandleShiftAndArrows()=" 5193 + nativePageShouldHandleShiftAndArrows() 5194 + " animateZoom=" + animateZoom 5195 + " extras=" + extras); 5196 } 5197 5198 calcOurContentVisibleRectF(mVisibleContentRect); 5199 if (canvas.isHardwareAccelerated()) { 5200 Rect glRectViewport = mGLViewportEmpty ? null : mGLRectViewport; 5201 Rect viewRectViewport = mGLViewportEmpty ? null : mViewRectViewport; 5202 5203 int functor = nativeGetDrawGLFunction(mNativeClass, glRectViewport, 5204 viewRectViewport, mVisibleContentRect, getScale(), extras); 5205 ((HardwareCanvas) canvas).callDrawGLFunction(functor); 5206 if (mHardwareAccelSkia != getSettings().getHardwareAccelSkiaEnabled()) { 5207 mHardwareAccelSkia = getSettings().getHardwareAccelSkiaEnabled(); 5208 nativeUseHardwareAccelSkia(mHardwareAccelSkia); 5209 } 5210 5211 } else { 5212 DrawFilter df = null; 5213 if (mZoomManager.isZoomAnimating() || UIAnimationsRunning) { 5214 df = mZoomFilter; 5215 } else if (animateScroll) { 5216 df = mScrollFilter; 5217 } 5218 canvas.setDrawFilter(df); 5219 // XXX: Revisit splitting content. Right now it causes a 5220 // synchronization problem with layers. 5221 int content = nativeDraw(canvas, mVisibleContentRect, color, 5222 extras, false); 5223 canvas.setDrawFilter(null); 5224 if (!mBlockWebkitViewMessages && content != 0) { 5225 mWebViewCore.sendMessage(EventHub.SPLIT_PICTURE_SET, content, 0); 5226 } 5227 } 5228 5229 canvas.restoreToCount(saveCount); 5230 if (mSelectingText) { 5231 drawTextSelectionHandles(canvas); 5232 } 5233 5234 if (extras == DRAW_EXTRAS_CURSOR_RING) { 5235 if (mTouchMode == TOUCH_SHORTPRESS_START_MODE) { 5236 mTouchMode = TOUCH_SHORTPRESS_MODE; 5237 } 5238 } 5239 if (mFocusSizeChanged) { 5240 mFocusSizeChanged = false; 5241 // If we are zooming, this will get handled above, when the zoom 5242 // finishes. We also do not need to do this unless the WebTextView 5243 // is showing. With hardware acceleration, the pageSwapCallback() 5244 // updates the WebTextView position in sync with page swapping 5245 if (!canvas.isHardwareAccelerated() && !animateZoom && inEditingMode()) { 5246 didUpdateWebTextViewDimensions(ANYWHERE); 5247 } 5248 } 5249 } 5250 5251 private void drawTextSelectionHandles(Canvas canvas) { 5252 int[] handles = new int[4]; 5253 getSelectionHandles(handles); 5254 int start_x = contentToViewDimension(handles[0]); 5255 int start_y = contentToViewDimension(handles[1]); 5256 int end_x = contentToViewDimension(handles[2]); 5257 int end_y = contentToViewDimension(handles[3]); 5258 5259 if (mIsCaretSelection) { 5260 if (mSelectHandleCenter == null) { 5261 mSelectHandleCenter = mContext.getResources().getDrawable( 5262 com.android.internal.R.drawable.text_select_handle_middle); 5263 } 5264 // Caret handle is centered 5265 start_x -= (mSelectHandleCenter.getIntrinsicWidth() / 2); 5266 mSelectHandleCenter.setBounds(start_x, start_y, 5267 start_x + mSelectHandleCenter.getIntrinsicWidth(), 5268 start_y + mSelectHandleCenter.getIntrinsicHeight()); 5269 mSelectHandleCenter.draw(canvas); 5270 } else { 5271 if (mSelectHandleLeft == null) { 5272 mSelectHandleLeft = mContext.getResources().getDrawable( 5273 com.android.internal.R.drawable.text_select_handle_left); 5274 } 5275 // Magic formula copied from TextView 5276 start_x -= (mSelectHandleLeft.getIntrinsicWidth() * 3) / 4; 5277 mSelectHandleLeft.setBounds(start_x, start_y, 5278 start_x + mSelectHandleLeft.getIntrinsicWidth(), 5279 start_y + mSelectHandleLeft.getIntrinsicHeight()); 5280 if (mSelectHandleRight == null) { 5281 mSelectHandleRight = mContext.getResources().getDrawable( 5282 com.android.internal.R.drawable.text_select_handle_right); 5283 } 5284 end_x -= mSelectHandleRight.getIntrinsicWidth() / 4; 5285 mSelectHandleRight.setBounds(end_x, end_y, 5286 end_x + mSelectHandleRight.getIntrinsicWidth(), 5287 end_y + mSelectHandleRight.getIntrinsicHeight()); 5288 mSelectHandleLeft.draw(canvas); 5289 mSelectHandleRight.draw(canvas); 5290 } 5291 } 5292 5293 /** 5294 * Takes an int[4] array as an output param with the values being 5295 * startX, startY, endX, endY 5296 */ 5297 private void getSelectionHandles(int[] handles) { 5298 handles[0] = mSelectCursorBase.right; 5299 handles[1] = mSelectCursorBase.bottom - 5300 (mSelectCursorBase.height() / 4); 5301 handles[2] = mSelectCursorExtent.left; 5302 handles[3] = mSelectCursorExtent.bottom 5303 - (mSelectCursorExtent.height() / 4); 5304 if (!nativeIsBaseFirst(mNativeClass)) { 5305 int swap = handles[0]; 5306 handles[0] = handles[2]; 5307 handles[2] = swap; 5308 swap = handles[1]; 5309 handles[1] = handles[3]; 5310 handles[3] = swap; 5311 } 5312 } 5313 5314 // draw history 5315 private boolean mDrawHistory = false; 5316 private Picture mHistoryPicture = null; 5317 private int mHistoryWidth = 0; 5318 private int mHistoryHeight = 0; 5319 5320 // Only check the flag, can be called from WebCore thread 5321 boolean drawHistory() { 5322 return mDrawHistory; 5323 } 5324 5325 int getHistoryPictureWidth() { 5326 return (mHistoryPicture != null) ? mHistoryPicture.getWidth() : 0; 5327 } 5328 5329 // Should only be called in UI thread 5330 void switchOutDrawHistory() { 5331 if (null == mWebViewCore) return; // CallbackProxy may trigger this 5332 if (mDrawHistory && (getProgress() == 100 || nativeHasContent())) { 5333 mDrawHistory = false; 5334 mHistoryPicture = null; 5335 invalidate(); 5336 int oldScrollX = mScrollX; 5337 int oldScrollY = mScrollY; 5338 mScrollX = pinLocX(mScrollX); 5339 mScrollY = pinLocY(mScrollY); 5340 if (oldScrollX != mScrollX || oldScrollY != mScrollY) { 5341 onScrollChanged(mScrollX, mScrollY, oldScrollX, oldScrollY); 5342 } else { 5343 sendOurVisibleRect(); 5344 } 5345 } 5346 } 5347 5348 // TODO: Remove this 5349 WebViewCore.CursorData cursorData() { 5350 if (sDisableNavcache) { 5351 return new WebViewCore.CursorData(0, 0, 0, 0); 5352 } 5353 WebViewCore.CursorData result = cursorDataNoPosition(); 5354 Point position = nativeCursorPosition(); 5355 result.mX = position.x; 5356 result.mY = position.y; 5357 return result; 5358 } 5359 5360 WebViewCore.CursorData cursorDataNoPosition() { 5361 WebViewCore.CursorData result = new WebViewCore.CursorData(); 5362 result.mMoveGeneration = nativeMoveGeneration(); 5363 result.mFrame = nativeCursorFramePointer(); 5364 return result; 5365 } 5366 5367 /** 5368 * Delete text from start to end in the focused textfield. If there is no 5369 * focus, or if start == end, silently fail. If start and end are out of 5370 * order, swap them. 5371 * @param start Beginning of selection to delete. 5372 * @param end End of selection to delete. 5373 */ 5374 /* package */ void deleteSelection(int start, int end) { 5375 mTextGeneration++; 5376 WebViewCore.TextSelectionData data 5377 = new WebViewCore.TextSelectionData(start, end, 0); 5378 mWebViewCore.sendMessage(EventHub.DELETE_SELECTION, mTextGeneration, 0, 5379 data); 5380 } 5381 5382 /** 5383 * Set the selection to (start, end) in the focused textfield. If start and 5384 * end are out of order, swap them. 5385 * @param start Beginning of selection. 5386 * @param end End of selection. 5387 */ 5388 /* package */ void setSelection(int start, int end) { 5389 if (mWebViewCore != null) { 5390 mWebViewCore.sendMessage(EventHub.SET_SELECTION, start, end); 5391 } 5392 } 5393 5394 @Override 5395 public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 5396 if (mInputConnection == null) { 5397 mInputConnection = new WebViewInputConnection(); 5398 } 5399 mInputConnection.setupEditorInfo(outAttrs); 5400 return mInputConnection; 5401 } 5402 5403 /** 5404 * Called in response to a message from webkit telling us that the soft 5405 * keyboard should be launched. 5406 */ 5407 private void displaySoftKeyboard(boolean isTextView) { 5408 InputMethodManager imm = (InputMethodManager) 5409 getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 5410 5411 // bring it back to the default level scale so that user can enter text 5412 boolean zoom = mZoomManager.getScale() < mZoomManager.getDefaultScale(); 5413 if (zoom) { 5414 mZoomManager.setZoomCenter(mLastTouchX, mLastTouchY); 5415 mZoomManager.setZoomScale(mZoomManager.getDefaultScale(), false); 5416 } 5417 if (isTextView) { 5418 rebuildWebTextView(); 5419 if (inEditingMode()) { 5420 imm.showSoftInput(mWebTextView, 0, mWebTextView.getResultReceiver()); 5421 if (zoom) { 5422 didUpdateWebTextViewDimensions(INTERSECTS_SCREEN); 5423 } 5424 return; 5425 } 5426 } 5427 // Used by plugins and contentEditable. 5428 // Also used if the navigation cache is out of date, and 5429 // does not recognize that a textfield is in focus. In that 5430 // case, use WebView as the targeted view. 5431 // see http://b/issue?id=2457459 5432 imm.showSoftInput(this, 0); 5433 } 5434 5435 // Called by WebKit to instruct the UI to hide the keyboard 5436 private void hideSoftKeyboard() { 5437 InputMethodManager imm = InputMethodManager.peekInstance(); 5438 if (imm != null && (imm.isActive(this) 5439 || (inEditingMode() && imm.isActive(mWebTextView)))) { 5440 imm.hideSoftInputFromWindow(this.getWindowToken(), 0); 5441 } 5442 } 5443 5444 /* 5445 * This method checks the current focus and cursor and potentially rebuilds 5446 * mWebTextView to have the appropriate properties, such as password, 5447 * multiline, and what text it contains. It also removes it if necessary. 5448 */ 5449 /* package */ void rebuildWebTextView() { 5450 if (!sEnableWebTextView) { 5451 return; // always use WebKit's text entry 5452 } 5453 // If the WebView does not have focus, do nothing until it gains focus. 5454 if (!hasFocus() && (null == mWebTextView || !mWebTextView.hasFocus())) { 5455 return; 5456 } 5457 boolean alreadyThere = inEditingMode(); 5458 // inEditingMode can only return true if mWebTextView is non-null, 5459 // so we can safely call remove() if (alreadyThere) 5460 if (0 == mNativeClass || !nativeFocusCandidateIsTextInput()) { 5461 if (alreadyThere) { 5462 mWebTextView.remove(); 5463 } 5464 return; 5465 } 5466 // At this point, we know we have found an input field, so go ahead 5467 // and create the WebTextView if necessary. 5468 if (mWebTextView == null) { 5469 mWebTextView = new WebTextView(mContext, WebView.this, mAutoFillData.getQueryId()); 5470 // Initialize our generation number. 5471 mTextGeneration = 0; 5472 } 5473 mWebTextView.updateTextSize(); 5474 updateWebTextViewPosition(); 5475 String text = nativeFocusCandidateText(); 5476 int nodePointer = nativeFocusCandidatePointer(); 5477 // This needs to be called before setType, which may call 5478 // requestFormData, and it needs to have the correct nodePointer. 5479 mWebTextView.setNodePointer(nodePointer); 5480 mWebTextView.setType(nativeFocusCandidateType()); 5481 // Gravity needs to be set after setType 5482 mWebTextView.setGravityForRtl(nativeFocusCandidateIsRtlText()); 5483 if (null == text) { 5484 if (DebugFlags.WEB_VIEW) { 5485 Log.v(LOGTAG, "rebuildWebTextView null == text"); 5486 } 5487 text = ""; 5488 } 5489 mWebTextView.setTextAndKeepSelection(text); 5490 InputMethodManager imm = InputMethodManager.peekInstance(); 5491 if (imm != null && imm.isActive(mWebTextView)) { 5492 imm.restartInput(mWebTextView); 5493 mWebTextView.clearComposingText(); 5494 } 5495 if (isFocused()) { 5496 mWebTextView.requestFocus(); 5497 } 5498 } 5499 5500 private void updateWebTextViewPosition() { 5501 Rect visibleRect = new Rect(); 5502 calcOurContentVisibleRect(visibleRect); 5503 // Note that sendOurVisibleRect calls viewToContent, so the coordinates 5504 // should be in content coordinates. 5505 Rect bounds = nativeFocusCandidateNodeBounds(); 5506 Rect vBox = contentToViewRect(bounds); 5507 offsetByLayerScrollPosition(vBox); 5508 mWebTextView.setRect(vBox.left, vBox.top, vBox.width(), vBox.height()); 5509 if (!Rect.intersects(bounds, visibleRect)) { 5510 revealSelection(); 5511 } 5512 updateWebTextViewPadding(); 5513 } 5514 5515 /** 5516 * Update the padding of mWebTextView based on the native textfield/textarea 5517 */ 5518 void updateWebTextViewPadding() { 5519 Rect paddingRect = nativeFocusCandidatePaddingRect(); 5520 if (paddingRect != null) { 5521 // Use contentToViewDimension since these are the dimensions of 5522 // the padding. 5523 mWebTextView.setPadding( 5524 contentToViewDimension(paddingRect.left), 5525 contentToViewDimension(paddingRect.top), 5526 contentToViewDimension(paddingRect.right), 5527 contentToViewDimension(paddingRect.bottom)); 5528 } 5529 } 5530 5531 /** 5532 * Tell webkit to put the cursor on screen. 5533 */ 5534 /* package */ void revealSelection() { 5535 if (mWebViewCore != null) { 5536 mWebViewCore.sendMessage(EventHub.REVEAL_SELECTION); 5537 } 5538 } 5539 5540 /** 5541 * Called by WebTextView to find saved form data associated with the 5542 * textfield 5543 * @param name Name of the textfield. 5544 * @param nodePointer Pointer to the node of the textfield, so it can be 5545 * compared to the currently focused textfield when the data is 5546 * retrieved. 5547 * @param autoFillable true if WebKit has determined this field is part of 5548 * a form that can be auto filled. 5549 * @param autoComplete true if the attribute "autocomplete" is set to true 5550 * on the textfield. 5551 */ 5552 /* package */ void requestFormData(String name, int nodePointer, 5553 boolean autoFillable, boolean autoComplete) { 5554 if (mWebViewCore.getSettings().getSaveFormData()) { 5555 Message update = mPrivateHandler.obtainMessage(REQUEST_FORM_DATA); 5556 update.arg1 = nodePointer; 5557 RequestFormData updater = new RequestFormData(name, getUrl(), 5558 update, autoFillable, autoComplete); 5559 Thread t = new Thread(updater); 5560 t.start(); 5561 } 5562 } 5563 5564 /** 5565 * Pass a message to find out the <label> associated with the <input> 5566 * identified by nodePointer 5567 * @param framePointer Pointer to the frame containing the <input> node 5568 * @param nodePointer Pointer to the node for which a <label> is desired. 5569 */ 5570 /* package */ void requestLabel(int framePointer, int nodePointer) { 5571 mWebViewCore.sendMessage(EventHub.REQUEST_LABEL, framePointer, 5572 nodePointer); 5573 } 5574 5575 /* 5576 * This class requests an Adapter for the WebTextView which shows past 5577 * entries stored in the database. It is a Runnable so that it can be done 5578 * in its own thread, without slowing down the UI. 5579 */ 5580 private class RequestFormData implements Runnable { 5581 private String mName; 5582 private String mUrl; 5583 private Message mUpdateMessage; 5584 private boolean mAutoFillable; 5585 private boolean mAutoComplete; 5586 private WebSettings mWebSettings; 5587 5588 public RequestFormData(String name, String url, Message msg, 5589 boolean autoFillable, boolean autoComplete) { 5590 mName = name; 5591 mUrl = WebTextView.urlForAutoCompleteData(url); 5592 mUpdateMessage = msg; 5593 mAutoFillable = autoFillable; 5594 mAutoComplete = autoComplete; 5595 mWebSettings = getSettings(); 5596 } 5597 5598 @Override 5599 public void run() { 5600 ArrayList<String> pastEntries = new ArrayList<String>(); 5601 5602 if (mAutoFillable) { 5603 // Note that code inside the adapter click handler in WebTextView depends 5604 // on the AutoFill item being at the top of the drop down list. If you change 5605 // the order, make sure to do it there too! 5606 if (mWebSettings != null && mWebSettings.getAutoFillProfile() != null) { 5607 pastEntries.add(getResources().getText( 5608 com.android.internal.R.string.autofill_this_form).toString() + 5609 " " + 5610 mAutoFillData.getPreviewString()); 5611 mWebTextView.setAutoFillProfileIsSet(true); 5612 } else { 5613 // There is no autofill profile set up yet, so add an option that 5614 // will invite the user to set their profile up. 5615 pastEntries.add(getResources().getText( 5616 com.android.internal.R.string.setup_autofill).toString()); 5617 mWebTextView.setAutoFillProfileIsSet(false); 5618 } 5619 } 5620 5621 if (mAutoComplete) { 5622 pastEntries.addAll(mDatabase.getFormData(mUrl, mName)); 5623 } 5624 5625 if (pastEntries.size() > 0) { 5626 AutoCompleteAdapter adapter = new 5627 AutoCompleteAdapter(mContext, pastEntries); 5628 mUpdateMessage.obj = adapter; 5629 mUpdateMessage.sendToTarget(); 5630 } 5631 } 5632 } 5633 5634 /** 5635 * Dump the display tree to "/sdcard/displayTree.txt" 5636 * 5637 * @hide debug only 5638 */ 5639 public void dumpDisplayTree() { 5640 nativeDumpDisplayTree(getUrl()); 5641 } 5642 5643 /** 5644 * Dump the dom tree to adb shell if "toFile" is False, otherwise dump it to 5645 * "/sdcard/domTree.txt" 5646 * 5647 * @hide debug only 5648 */ 5649 public void dumpDomTree(boolean toFile) { 5650 mWebViewCore.sendMessage(EventHub.DUMP_DOMTREE, toFile ? 1 : 0, 0); 5651 } 5652 5653 /** 5654 * Dump the render tree to adb shell if "toFile" is False, otherwise dump it 5655 * to "/sdcard/renderTree.txt" 5656 * 5657 * @hide debug only 5658 */ 5659 public void dumpRenderTree(boolean toFile) { 5660 mWebViewCore.sendMessage(EventHub.DUMP_RENDERTREE, toFile ? 1 : 0, 0); 5661 } 5662 5663 /** 5664 * Called by DRT on UI thread, need to proxy to WebCore thread. 5665 * 5666 * @hide debug only 5667 */ 5668 public void useMockDeviceOrientation() { 5669 mWebViewCore.sendMessage(EventHub.USE_MOCK_DEVICE_ORIENTATION); 5670 } 5671 5672 /** 5673 * Called by DRT on WebCore thread. 5674 * 5675 * @hide debug only 5676 */ 5677 public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha, 5678 boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) { 5679 mWebViewCore.setMockDeviceOrientation(canProvideAlpha, alpha, canProvideBeta, beta, 5680 canProvideGamma, gamma); 5681 } 5682 5683 // This is used to determine long press with the center key. Does not 5684 // affect long press with the trackball/touch. 5685 private boolean mGotCenterDown = false; 5686 5687 @Override 5688 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { 5689 if (mBlockWebkitViewMessages) { 5690 return false; 5691 } 5692 // send complex characters to webkit for use by JS and plugins 5693 if (keyCode == KeyEvent.KEYCODE_UNKNOWN && event.getCharacters() != null) { 5694 // pass the key to DOM 5695 mWebViewCore.sendMessage(EventHub.KEY_DOWN, event); 5696 mWebViewCore.sendMessage(EventHub.KEY_UP, event); 5697 // return true as DOM handles the key 5698 return true; 5699 } 5700 return false; 5701 } 5702 5703 private boolean isEnterActionKey(int keyCode) { 5704 return keyCode == KeyEvent.KEYCODE_DPAD_CENTER 5705 || keyCode == KeyEvent.KEYCODE_ENTER 5706 || keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER; 5707 } 5708 5709 @Override 5710 public boolean onKeyDown(int keyCode, KeyEvent event) { 5711 if (DebugFlags.WEB_VIEW) { 5712 Log.v(LOGTAG, "keyDown at " + System.currentTimeMillis() 5713 + "keyCode=" + keyCode 5714 + ", " + event + ", unicode=" + event.getUnicodeChar()); 5715 } 5716 if (mIsCaretSelection) { 5717 selectionDone(); 5718 } 5719 if (mBlockWebkitViewMessages) { 5720 return false; 5721 } 5722 5723 // don't implement accelerator keys here; defer to host application 5724 if (event.isCtrlPressed()) { 5725 return false; 5726 } 5727 5728 if (mNativeClass == 0) { 5729 return false; 5730 } 5731 5732 // do this hack up front, so it always works, regardless of touch-mode 5733 if (AUTO_REDRAW_HACK && (keyCode == KeyEvent.KEYCODE_CALL)) { 5734 mAutoRedraw = !mAutoRedraw; 5735 if (mAutoRedraw) { 5736 invalidate(); 5737 } 5738 return true; 5739 } 5740 5741 // Bubble up the key event if 5742 // 1. it is a system key; or 5743 // 2. the host application wants to handle it; 5744 if (event.isSystem() 5745 || mCallbackProxy.uiOverrideKeyEvent(event)) { 5746 return false; 5747 } 5748 5749 // accessibility support 5750 if (accessibilityScriptInjected()) { 5751 if (AccessibilityManager.getInstance(mContext).isEnabled()) { 5752 // if an accessibility script is injected we delegate to it the key handling. 5753 // this script is a screen reader which is a fully fledged solution for blind 5754 // users to navigate in and interact with web pages. 5755 mWebViewCore.sendMessage(EventHub.KEY_DOWN, event); 5756 return true; 5757 } else { 5758 // Clean up if accessibility was disabled after loading the current URL. 5759 mAccessibilityScriptInjected = false; 5760 } 5761 } else if (mAccessibilityInjector != null) { 5762 if (AccessibilityManager.getInstance(mContext).isEnabled()) { 5763 if (mAccessibilityInjector.onKeyEvent(event)) { 5764 // if an accessibility injector is present (no JavaScript enabled or the site 5765 // opts out injecting our JavaScript screen reader) we let it decide whether 5766 // to act on and consume the event. 5767 return true; 5768 } 5769 } else { 5770 // Clean up if accessibility was disabled after loading the current URL. 5771 mAccessibilityInjector = null; 5772 } 5773 } 5774 5775 if (keyCode == KeyEvent.KEYCODE_PAGE_UP) { 5776 if (event.hasNoModifiers()) { 5777 pageUp(false); 5778 return true; 5779 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) { 5780 pageUp(true); 5781 return true; 5782 } 5783 } 5784 5785 if (keyCode == KeyEvent.KEYCODE_PAGE_DOWN) { 5786 if (event.hasNoModifiers()) { 5787 pageDown(false); 5788 return true; 5789 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) { 5790 pageDown(true); 5791 return true; 5792 } 5793 } 5794 5795 if (keyCode == KeyEvent.KEYCODE_MOVE_HOME && event.hasNoModifiers()) { 5796 pageUp(true); 5797 return true; 5798 } 5799 5800 if (keyCode == KeyEvent.KEYCODE_MOVE_END && event.hasNoModifiers()) { 5801 pageDown(true); 5802 return true; 5803 } 5804 5805 if (keyCode >= KeyEvent.KEYCODE_DPAD_UP 5806 && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) { 5807 switchOutDrawHistory(); 5808 if (nativePageShouldHandleShiftAndArrows()) { 5809 letPageHandleNavKey(keyCode, event.getEventTime(), true, event.getMetaState()); 5810 return true; 5811 } 5812 if (event.hasModifiers(KeyEvent.META_ALT_ON)) { 5813 switch (keyCode) { 5814 case KeyEvent.KEYCODE_DPAD_UP: 5815 pageUp(true); 5816 return true; 5817 case KeyEvent.KEYCODE_DPAD_DOWN: 5818 pageDown(true); 5819 return true; 5820 case KeyEvent.KEYCODE_DPAD_LEFT: 5821 nativeClearCursor(); // start next trackball movement from page edge 5822 return pinScrollTo(0, mScrollY, true, 0); 5823 case KeyEvent.KEYCODE_DPAD_RIGHT: 5824 nativeClearCursor(); // start next trackball movement from page edge 5825 return pinScrollTo(mContentWidth, mScrollY, true, 0); 5826 } 5827 } 5828 if (navHandledKey(keyCode, 1, false, event.getEventTime())) { 5829 playSoundEffect(keyCodeToSoundsEffect(keyCode)); 5830 return true; 5831 } 5832 // Bubble up the key event as WebView doesn't handle it 5833 return false; 5834 } 5835 5836 if (isEnterActionKey(keyCode)) { 5837 switchOutDrawHistory(); 5838 boolean wantsKeyEvents = nativeCursorNodePointer() == 0 5839 || nativeCursorWantsKeyEvents(); 5840 if (event.getRepeatCount() == 0) { 5841 if (mSelectingText) { 5842 return true; // discard press if copy in progress 5843 } 5844 mGotCenterDown = true; 5845 mPrivateHandler.sendMessageDelayed(mPrivateHandler 5846 .obtainMessage(LONG_PRESS_CENTER), LONG_PRESS_TIMEOUT); 5847 if (!wantsKeyEvents) return true; 5848 } 5849 // Bubble up the key event as WebView doesn't handle it 5850 if (!wantsKeyEvents) return false; 5851 } 5852 5853 if (getSettings().getNavDump()) { 5854 switch (keyCode) { 5855 case KeyEvent.KEYCODE_4: 5856 dumpDisplayTree(); 5857 break; 5858 case KeyEvent.KEYCODE_5: 5859 case KeyEvent.KEYCODE_6: 5860 dumpDomTree(keyCode == KeyEvent.KEYCODE_5); 5861 break; 5862 case KeyEvent.KEYCODE_7: 5863 case KeyEvent.KEYCODE_8: 5864 dumpRenderTree(keyCode == KeyEvent.KEYCODE_7); 5865 break; 5866 } 5867 } 5868 5869 if (nativeCursorIsTextInput()) { 5870 // This message will put the node in focus, for the DOM's notion 5871 // of focus. 5872 mWebViewCore.sendMessage(EventHub.FAKE_CLICK, nativeCursorFramePointer(), 5873 nativeCursorNodePointer()); 5874 // This will bring up the WebTextView and put it in focus, for 5875 // our view system's notion of focus 5876 rebuildWebTextView(); 5877 // Now we need to pass the event to it 5878 if (inEditingMode()) { 5879 mWebTextView.setDefaultSelection(); 5880 return mWebTextView.dispatchKeyEvent(event); 5881 } 5882 } else if (nativeHasFocusNode()) { 5883 // In this case, the cursor is not on a text input, but the focus 5884 // might be. Check it, and if so, hand over to the WebTextView. 5885 rebuildWebTextView(); 5886 if (inEditingMode()) { 5887 mWebTextView.setDefaultSelection(); 5888 return mWebTextView.dispatchKeyEvent(event); 5889 } 5890 } 5891 5892 // TODO: should we pass all the keys to DOM or check the meta tag 5893 if (nativeCursorWantsKeyEvents() || true) { 5894 // pass the key to DOM 5895 mWebViewCore.sendMessage(EventHub.KEY_DOWN, event); 5896 // return true as DOM handles the key 5897 return true; 5898 } 5899 5900 // Bubble up the key event as WebView doesn't handle it 5901 return false; 5902 } 5903 5904 @Override 5905 public boolean onKeyUp(int keyCode, KeyEvent event) { 5906 if (DebugFlags.WEB_VIEW) { 5907 Log.v(LOGTAG, "keyUp at " + System.currentTimeMillis() 5908 + ", " + event + ", unicode=" + event.getUnicodeChar()); 5909 } 5910 if (mBlockWebkitViewMessages) { 5911 return false; 5912 } 5913 5914 if (mNativeClass == 0) { 5915 return false; 5916 } 5917 5918 // special CALL handling when cursor node's href is "tel:XXX" 5919 if (keyCode == KeyEvent.KEYCODE_CALL && nativeHasCursorNode()) { 5920 String text = nativeCursorText(); 5921 if (!nativeCursorIsTextInput() && text != null 5922 && text.startsWith(SCHEME_TEL)) { 5923 Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(text)); 5924 getContext().startActivity(intent); 5925 return true; 5926 } 5927 } 5928 5929 // Bubble up the key event if 5930 // 1. it is a system key; or 5931 // 2. the host application wants to handle it; 5932 if (event.isSystem() 5933 || mCallbackProxy.uiOverrideKeyEvent(event)) { 5934 return false; 5935 } 5936 5937 // accessibility support 5938 if (accessibilityScriptInjected()) { 5939 if (AccessibilityManager.getInstance(mContext).isEnabled()) { 5940 // if an accessibility script is injected we delegate to it the key handling. 5941 // this script is a screen reader which is a fully fledged solution for blind 5942 // users to navigate in and interact with web pages. 5943 mWebViewCore.sendMessage(EventHub.KEY_UP, event); 5944 return true; 5945 } else { 5946 // Clean up if accessibility was disabled after loading the current URL. 5947 mAccessibilityScriptInjected = false; 5948 } 5949 } else if (mAccessibilityInjector != null) { 5950 if (AccessibilityManager.getInstance(mContext).isEnabled()) { 5951 if (mAccessibilityInjector.onKeyEvent(event)) { 5952 // if an accessibility injector is present (no JavaScript enabled or the site 5953 // opts out injecting our JavaScript screen reader) we let it decide whether to 5954 // act on and consume the event. 5955 return true; 5956 } 5957 } else { 5958 // Clean up if accessibility was disabled after loading the current URL. 5959 mAccessibilityInjector = null; 5960 } 5961 } 5962 5963 if (keyCode >= KeyEvent.KEYCODE_DPAD_UP 5964 && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) { 5965 if (nativePageShouldHandleShiftAndArrows()) { 5966 letPageHandleNavKey(keyCode, event.getEventTime(), false, event.getMetaState()); 5967 return true; 5968 } 5969 // always handle the navigation keys in the UI thread 5970 // Bubble up the key event as WebView doesn't handle it 5971 return false; 5972 } 5973 5974 if (isEnterActionKey(keyCode)) { 5975 // remove the long press message first 5976 mPrivateHandler.removeMessages(LONG_PRESS_CENTER); 5977 mGotCenterDown = false; 5978 5979 if (mSelectingText) { 5980 copySelection(); 5981 selectionDone(); 5982 return true; // discard press if copy in progress 5983 } 5984 5985 if (!sDisableNavcache) { 5986 // perform the single click 5987 Rect visibleRect = sendOurVisibleRect(); 5988 // Note that sendOurVisibleRect calls viewToContent, so the 5989 // coordinates should be in content coordinates. 5990 if (!nativeCursorIntersects(visibleRect)) { 5991 return false; 5992 } 5993 WebViewCore.CursorData data = cursorData(); 5994 mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, data); 5995 playSoundEffect(SoundEffectConstants.CLICK); 5996 if (nativeCursorIsTextInput()) { 5997 rebuildWebTextView(); 5998 centerKeyPressOnTextField(); 5999 if (inEditingMode()) { 6000 mWebTextView.setDefaultSelection(); 6001 } 6002 return true; 6003 } 6004 clearTextEntry(); 6005 nativeShowCursorTimed(); 6006 if (mCallbackProxy.uiOverrideUrlLoading(nativeCursorText())) { 6007 return true; 6008 } 6009 if (nativeCursorNodePointer() != 0 && !nativeCursorWantsKeyEvents()) { 6010 mWebViewCore.sendMessage(EventHub.CLICK, data.mFrame, 6011 nativeCursorNodePointer()); 6012 return true; 6013 } 6014 } 6015 } 6016 6017 // TODO: should we pass all the keys to DOM or check the meta tag 6018 if (nativeCursorWantsKeyEvents() || true) { 6019 // pass the key to DOM 6020 mWebViewCore.sendMessage(EventHub.KEY_UP, event); 6021 // return true as DOM handles the key 6022 return true; 6023 } 6024 6025 // Bubble up the key event as WebView doesn't handle it 6026 return false; 6027 } 6028 6029 private boolean startSelectActionMode() { 6030 mSelectCallback = new SelectActionModeCallback(); 6031 mSelectCallback.setTextSelected(!mIsCaretSelection); 6032 mSelectCallback.setWebView(this); 6033 if (startActionMode(mSelectCallback) == null) { 6034 // There is no ActionMode, so do not allow the user to modify a 6035 // selection. 6036 selectionDone(); 6037 return false; 6038 } 6039 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 6040 return true; 6041 } 6042 6043 private void showPasteWindow() { 6044 ClipboardManager cm = (ClipboardManager)(mContext 6045 .getSystemService(Context.CLIPBOARD_SERVICE)); 6046 if (cm.hasPrimaryClip()) { 6047 Rect cursorRect = contentToViewRect(mSelectCursorBase); 6048 int[] location = new int[2]; 6049 getLocationInWindow(location); 6050 cursorRect.offset(location[0] - mScrollX, location[1] - mScrollY); 6051 if (mPasteWindow == null) { 6052 mPasteWindow = new PastePopupWindow(); 6053 } 6054 mPasteWindow.show(cursorRect, location[0], location[1]); 6055 } 6056 } 6057 6058 private void hidePasteButton() { 6059 if (mPasteWindow != null) { 6060 mPasteWindow.hide(); 6061 } 6062 } 6063 6064 private void syncSelectionCursors() { 6065 mSelectCursorBaseLayerId = 6066 nativeGetHandleLayerId(mNativeClass, HANDLE_ID_BASE, mSelectCursorBase); 6067 mSelectCursorExtentLayerId = 6068 nativeGetHandleLayerId(mNativeClass, HANDLE_ID_EXTENT, mSelectCursorExtent); 6069 } 6070 6071 private boolean setupWebkitSelect() { 6072 syncSelectionCursors(); 6073 if (mIsCaretSelection) { 6074 showPasteWindow(); 6075 } else if (!startSelectActionMode()) { 6076 selectionDone(); 6077 return false; 6078 } 6079 mSelectingText = true; 6080 mTouchMode = TOUCH_DRAG_MODE; 6081 return true; 6082 } 6083 6084 private void updateWebkitSelection() { 6085 int[] handles = null; 6086 if (mIsCaretSelection) { 6087 mSelectCursorExtent.set(mSelectCursorBase); 6088 } 6089 if (mSelectingText) { 6090 handles = new int[4]; 6091 handles[0] = mSelectCursorBase.centerX(); 6092 handles[1] = mSelectCursorBase.centerY(); 6093 handles[2] = mSelectCursorExtent.centerX(); 6094 handles[3] = mSelectCursorExtent.centerY(); 6095 } else { 6096 nativeSetTextSelection(mNativeClass, 0); 6097 } 6098 mWebViewCore.removeMessages(EventHub.SELECT_TEXT); 6099 mWebViewCore.sendMessageAtFrontOfQueue(EventHub.SELECT_TEXT, handles); 6100 } 6101 6102 private void resetCaretTimer() { 6103 mPrivateHandler.removeMessages(CLEAR_CARET_HANDLE); 6104 if (!mSelectionStarted) { 6105 mPrivateHandler.sendEmptyMessageDelayed(CLEAR_CARET_HANDLE, 6106 CARET_HANDLE_STAMINA_MS); 6107 } 6108 } 6109 6110 /** 6111 * Use this method to put the WebView into text selection mode. 6112 * Do not rely on this functionality; it will be deprecated in the future. 6113 * @deprecated This method is now obsolete. 6114 */ 6115 @Deprecated 6116 public void emulateShiftHeld() { 6117 checkThread(); 6118 } 6119 6120 /** 6121 * Select all of the text in this WebView. 6122 * 6123 * @hide This is an implementation detail. 6124 */ 6125 public void selectAll() { 6126 mWebViewCore.sendMessage(EventHub.SELECT_ALL); 6127 } 6128 6129 /** 6130 * Called when the selection has been removed. 6131 */ 6132 void selectionDone() { 6133 if (mSelectingText) { 6134 hidePasteButton(); 6135 mSelectingText = false; 6136 // finish is idempotent, so this is fine even if selectionDone was 6137 // called by mSelectCallback.onDestroyActionMode 6138 if (mSelectCallback != null) { 6139 mSelectCallback.finish(); 6140 mSelectCallback = null; 6141 } 6142 if (!mIsCaretSelection) { 6143 updateWebkitSelection(); 6144 } 6145 mIsCaretSelection = false; 6146 invalidate(); // redraw without selection 6147 mAutoScrollX = 0; 6148 mAutoScrollY = 0; 6149 mSentAutoScrollMessage = false; 6150 } 6151 } 6152 6153 /** 6154 * Copy the selection to the clipboard 6155 * 6156 * @hide This is an implementation detail. 6157 */ 6158 public boolean copySelection() { 6159 boolean copiedSomething = false; 6160 String selection = getSelection(); 6161 if (selection != null && selection != "") { 6162 if (DebugFlags.WEB_VIEW) { 6163 Log.v(LOGTAG, "copySelection \"" + selection + "\""); 6164 } 6165 Toast.makeText(mContext 6166 , com.android.internal.R.string.text_copied 6167 , Toast.LENGTH_SHORT).show(); 6168 copiedSomething = true; 6169 ClipboardManager cm = (ClipboardManager)getContext() 6170 .getSystemService(Context.CLIPBOARD_SERVICE); 6171 cm.setText(selection); 6172 int[] handles = new int[4]; 6173 getSelectionHandles(handles); 6174 mWebViewCore.sendMessage(EventHub.COPY_TEXT, handles); 6175 } 6176 invalidate(); // remove selection region and pointer 6177 return copiedSomething; 6178 } 6179 6180 /** 6181 * Cut the selected text into the clipboard 6182 * 6183 * @hide This is an implementation detail 6184 */ 6185 public void cutSelection() { 6186 copySelection(); 6187 int[] handles = new int[4]; 6188 getSelectionHandles(handles); 6189 mWebViewCore.sendMessage(EventHub.DELETE_TEXT, handles); 6190 } 6191 6192 /** 6193 * Paste text from the clipboard to the cursor position. 6194 * 6195 * @hide This is an implementation detail 6196 */ 6197 public void pasteFromClipboard() { 6198 ClipboardManager cm = (ClipboardManager)getContext() 6199 .getSystemService(Context.CLIPBOARD_SERVICE); 6200 ClipData clipData = cm.getPrimaryClip(); 6201 if (clipData != null) { 6202 ClipData.Item clipItem = clipData.getItemAt(0); 6203 CharSequence pasteText = clipItem.getText(); 6204 if (mInputConnection != null) { 6205 mInputConnection.replaceSelection(pasteText); 6206 } 6207 } 6208 } 6209 6210 /** 6211 * @hide This is an implementation detail. 6212 */ 6213 public SearchBox getSearchBox() { 6214 if ((mWebViewCore == null) || (mWebViewCore.getBrowserFrame() == null)) { 6215 return null; 6216 } 6217 return mWebViewCore.getBrowserFrame().getSearchBox(); 6218 } 6219 6220 /** 6221 * Returns the currently highlighted text as a string. 6222 */ 6223 String getSelection() { 6224 if (mNativeClass == 0) return ""; 6225 return nativeGetSelection(); 6226 } 6227 6228 @Override 6229 protected void onAttachedToWindow() { 6230 super.onAttachedToWindow(); 6231 if (hasWindowFocus()) setActive(true); 6232 final ViewTreeObserver treeObserver = getViewTreeObserver(); 6233 if (mGlobalLayoutListener == null) { 6234 mGlobalLayoutListener = new InnerGlobalLayoutListener(); 6235 treeObserver.addOnGlobalLayoutListener(mGlobalLayoutListener); 6236 } 6237 if (mScrollChangedListener == null) { 6238 mScrollChangedListener = new InnerScrollChangedListener(); 6239 treeObserver.addOnScrollChangedListener(mScrollChangedListener); 6240 } 6241 6242 addAccessibilityApisToJavaScript(); 6243 6244 mTouchEventQueue.reset(); 6245 } 6246 6247 @Override 6248 protected void onDetachedFromWindow() { 6249 clearHelpers(); 6250 mZoomManager.dismissZoomPicker(); 6251 if (hasWindowFocus()) setActive(false); 6252 6253 final ViewTreeObserver treeObserver = getViewTreeObserver(); 6254 if (mGlobalLayoutListener != null) { 6255 treeObserver.removeGlobalOnLayoutListener(mGlobalLayoutListener); 6256 mGlobalLayoutListener = null; 6257 } 6258 if (mScrollChangedListener != null) { 6259 treeObserver.removeOnScrollChangedListener(mScrollChangedListener); 6260 mScrollChangedListener = null; 6261 } 6262 6263 removeAccessibilityApisFromJavaScript(); 6264 6265 super.onDetachedFromWindow(); 6266 } 6267 6268 @Override 6269 protected void onVisibilityChanged(View changedView, int visibility) { 6270 super.onVisibilityChanged(changedView, visibility); 6271 // The zoomManager may be null if the webview is created from XML that 6272 // specifies the view's visibility param as not visible (see http://b/2794841) 6273 if (visibility != View.VISIBLE && mZoomManager != null) { 6274 mZoomManager.dismissZoomPicker(); 6275 } 6276 updateDrawingState(); 6277 } 6278 6279 /** 6280 * @deprecated WebView no longer needs to implement 6281 * ViewGroup.OnHierarchyChangeListener. This method does nothing now. 6282 */ 6283 @Override 6284 // Cannot add @hide as this can always be accessed via the interface. 6285 @Deprecated 6286 public void onChildViewAdded(View parent, View child) {} 6287 6288 /** 6289 * @deprecated WebView no longer needs to implement 6290 * ViewGroup.OnHierarchyChangeListener. This method does nothing now. 6291 */ 6292 @Override 6293 // Cannot add @hide as this can always be accessed via the interface. 6294 @Deprecated 6295 public void onChildViewRemoved(View p, View child) {} 6296 6297 /** 6298 * @deprecated WebView should not have implemented 6299 * ViewTreeObserver.OnGlobalFocusChangeListener. This method does nothing now. 6300 */ 6301 @Override 6302 // Cannot add @hide as this can always be accessed via the interface. 6303 @Deprecated 6304 public void onGlobalFocusChanged(View oldFocus, View newFocus) { 6305 } 6306 6307 void setActive(boolean active) { 6308 if (active) { 6309 if (hasFocus()) { 6310 // If our window regained focus, and we have focus, then begin 6311 // drawing the cursor ring 6312 mDrawCursorRing = !inEditingMode(); 6313 setFocusControllerActive(true); 6314 } else { 6315 mDrawCursorRing = false; 6316 if (!inEditingMode()) { 6317 // If our window gained focus, but we do not have it, do not 6318 // draw the cursor ring. 6319 setFocusControllerActive(false); 6320 } 6321 // We do not call recordButtons here because we assume 6322 // that when we lost focus, or window focus, it got called with 6323 // false for the first parameter 6324 } 6325 } else { 6326 if (!mZoomManager.isZoomPickerVisible()) { 6327 /* 6328 * The external zoom controls come in their own window, so our 6329 * window loses focus. Our policy is to not draw the cursor ring 6330 * if our window is not focused, but this is an exception since 6331 * the user can still navigate the web page with the zoom 6332 * controls showing. 6333 */ 6334 mDrawCursorRing = false; 6335 } 6336 mKeysPressed.clear(); 6337 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); 6338 mTouchMode = TOUCH_DONE_MODE; 6339 setFocusControllerActive(false); 6340 } 6341 invalidate(); 6342 } 6343 6344 // To avoid drawing the cursor ring, and remove the TextView when our window 6345 // loses focus. 6346 @Override 6347 public void onWindowFocusChanged(boolean hasWindowFocus) { 6348 setActive(hasWindowFocus); 6349 if (hasWindowFocus) { 6350 JWebCoreJavaBridge.setActiveWebView(this); 6351 if (mPictureUpdatePausedForFocusChange) { 6352 WebViewCore.resumeUpdatePicture(mWebViewCore); 6353 mPictureUpdatePausedForFocusChange = false; 6354 } 6355 } else { 6356 JWebCoreJavaBridge.removeActiveWebView(this); 6357 final WebSettings settings = getSettings(); 6358 if (settings != null && settings.enableSmoothTransition() && 6359 mWebViewCore != null && !WebViewCore.isUpdatePicturePaused(mWebViewCore)) { 6360 WebViewCore.pauseUpdatePicture(mWebViewCore); 6361 mPictureUpdatePausedForFocusChange = true; 6362 } 6363 } 6364 super.onWindowFocusChanged(hasWindowFocus); 6365 } 6366 6367 /* 6368 * Pass a message to WebCore Thread, telling the WebCore::Page's 6369 * FocusController to be "inactive" so that it will 6370 * not draw the blinking cursor. It gets set to "active" to draw the cursor 6371 * in WebViewCore.cpp, when the WebCore thread receives key events/clicks. 6372 */ 6373 /* package */ void setFocusControllerActive(boolean active) { 6374 if (mWebViewCore == null) return; 6375 mWebViewCore.sendMessage(EventHub.SET_ACTIVE, active ? 1 : 0, 0); 6376 // Need to send this message after the document regains focus. 6377 if (active && mListBoxMessage != null) { 6378 mWebViewCore.sendMessage(mListBoxMessage); 6379 mListBoxMessage = null; 6380 } 6381 } 6382 6383 @Override 6384 protected void onFocusChanged(boolean focused, int direction, 6385 Rect previouslyFocusedRect) { 6386 if (DebugFlags.WEB_VIEW) { 6387 Log.v(LOGTAG, "MT focusChanged " + focused + ", " + direction); 6388 } 6389 if (focused) { 6390 // When we regain focus, if we have window focus, resume drawing 6391 // the cursor ring 6392 if (hasWindowFocus()) { 6393 mDrawCursorRing = !inEditingMode(); 6394 setFocusControllerActive(true); 6395 //} else { 6396 // The WebView has gained focus while we do not have 6397 // windowfocus. When our window lost focus, we should have 6398 // called recordButtons(false...) 6399 } 6400 } else { 6401 // When we lost focus, unless focus went to the TextView (which is 6402 // true if we are in editing mode), stop drawing the cursor ring. 6403 mDrawCursorRing = false; 6404 if (!inEditingMode()) { 6405 setFocusControllerActive(false); 6406 } 6407 mKeysPressed.clear(); 6408 } 6409 6410 super.onFocusChanged(focused, direction, previouslyFocusedRect); 6411 } 6412 6413 void setGLRectViewport() { 6414 // Use the getGlobalVisibleRect() to get the intersection among the parents 6415 // visible == false means we're clipped - send a null rect down to indicate that 6416 // we should not draw 6417 boolean visible = getGlobalVisibleRect(mGLRectViewport); 6418 if (visible) { 6419 // Then need to invert the Y axis, just for GL 6420 View rootView = getRootView(); 6421 int rootViewHeight = rootView.getHeight(); 6422 mViewRectViewport.set(mGLRectViewport); 6423 int savedWebViewBottom = mGLRectViewport.bottom; 6424 mGLRectViewport.bottom = rootViewHeight - mGLRectViewport.top - getVisibleTitleHeightImpl(); 6425 mGLRectViewport.top = rootViewHeight - savedWebViewBottom; 6426 mGLViewportEmpty = false; 6427 } else { 6428 mGLViewportEmpty = true; 6429 } 6430 calcOurContentVisibleRectF(mVisibleContentRect); 6431 nativeUpdateDrawGLFunction(mGLViewportEmpty ? null : mGLRectViewport, 6432 mGLViewportEmpty ? null : mViewRectViewport, 6433 mVisibleContentRect, getScale()); 6434 } 6435 6436 /** 6437 * @hide 6438 */ 6439 @Override 6440 protected boolean setFrame(int left, int top, int right, int bottom) { 6441 boolean changed = super.setFrame(left, top, right, bottom); 6442 if (!changed && mHeightCanMeasure) { 6443 // When mHeightCanMeasure is true, we will set mLastHeightSent to 0 6444 // in WebViewCore after we get the first layout. We do call 6445 // requestLayout() when we get contentSizeChanged(). But the View 6446 // system won't call onSizeChanged if the dimension is not changed. 6447 // In this case, we need to call sendViewSizeZoom() explicitly to 6448 // notify the WebKit about the new dimensions. 6449 sendViewSizeZoom(false); 6450 } 6451 setGLRectViewport(); 6452 return changed; 6453 } 6454 6455 @Override 6456 protected void onSizeChanged(int w, int h, int ow, int oh) { 6457 super.onSizeChanged(w, h, ow, oh); 6458 6459 // adjust the max viewport width depending on the view dimensions. This 6460 // is to ensure the scaling is not going insane. So do not shrink it if 6461 // the view size is temporarily smaller, e.g. when soft keyboard is up. 6462 int newMaxViewportWidth = (int) (Math.max(w, h) / mZoomManager.getDefaultMinZoomScale()); 6463 if (newMaxViewportWidth > sMaxViewportWidth) { 6464 sMaxViewportWidth = newMaxViewportWidth; 6465 } 6466 6467 mZoomManager.onSizeChanged(w, h, ow, oh); 6468 6469 if (mLoadedPicture != null && mDelaySetPicture == null) { 6470 // Size changes normally result in a new picture 6471 // Re-set the loaded picture to simulate that 6472 // However, do not update the base layer as that hasn't changed 6473 setNewPicture(mLoadedPicture, false); 6474 } 6475 } 6476 6477 @Override 6478 protected void onScrollChanged(int l, int t, int oldl, int oldt) { 6479 super.onScrollChanged(l, t, oldl, oldt); 6480 if (!mInOverScrollMode) { 6481 sendOurVisibleRect(); 6482 // update WebKit if visible title bar height changed. The logic is same 6483 // as getVisibleTitleHeightImpl. 6484 int titleHeight = getTitleHeight(); 6485 if (Math.max(titleHeight - t, 0) != Math.max(titleHeight - oldt, 0)) { 6486 sendViewSizeZoom(false); 6487 } 6488 } 6489 } 6490 6491 @Override 6492 public boolean dispatchKeyEvent(KeyEvent event) { 6493 switch (event.getAction()) { 6494 case KeyEvent.ACTION_DOWN: 6495 mKeysPressed.add(Integer.valueOf(event.getKeyCode())); 6496 break; 6497 case KeyEvent.ACTION_MULTIPLE: 6498 // Always accept the action. 6499 break; 6500 case KeyEvent.ACTION_UP: 6501 int location = mKeysPressed.indexOf(Integer.valueOf(event.getKeyCode())); 6502 if (location == -1) { 6503 // We did not receive the key down for this key, so do not 6504 // handle the key up. 6505 return false; 6506 } else { 6507 // We did receive the key down. Handle the key up, and 6508 // remove it from our pressed keys. 6509 mKeysPressed.remove(location); 6510 } 6511 break; 6512 default: 6513 // Accept the action. This should not happen, unless a new 6514 // action is added to KeyEvent. 6515 break; 6516 } 6517 if (inEditingMode() && mWebTextView.isFocused()) { 6518 // Ensure that the WebTextView gets the event, even if it does 6519 // not currently have a bounds. 6520 return mWebTextView.dispatchKeyEvent(event); 6521 } else { 6522 return super.dispatchKeyEvent(event); 6523 } 6524 } 6525 6526 /* 6527 * Here is the snap align logic: 6528 * 1. If it starts nearly horizontally or vertically, snap align; 6529 * 2. If there is a dramitic direction change, let it go; 6530 * 6531 * Adjustable parameters. Angle is the radians on a unit circle, limited 6532 * to quadrant 1. Values range from 0f (horizontal) to PI/2 (vertical) 6533 */ 6534 private static final float HSLOPE_TO_START_SNAP = .25f; 6535 private static final float HSLOPE_TO_BREAK_SNAP = .4f; 6536 private static final float VSLOPE_TO_START_SNAP = 1.25f; 6537 private static final float VSLOPE_TO_BREAK_SNAP = .95f; 6538 /* 6539 * These values are used to influence the average angle when entering 6540 * snap mode. If is is the first movement entering snap, we set the average 6541 * to the appropriate ideal. If the user is entering into snap after the 6542 * first movement, then we average the average angle with these values. 6543 */ 6544 private static final float ANGLE_VERT = 2f; 6545 private static final float ANGLE_HORIZ = 0f; 6546 /* 6547 * The modified moving average weight. 6548 * Formula: MAV[t]=MAV[t-1] + (P[t]-MAV[t-1])/n 6549 */ 6550 private static final float MMA_WEIGHT_N = 5; 6551 6552 private boolean hitFocusedPlugin(int contentX, int contentY) { 6553 if (DebugFlags.WEB_VIEW) { 6554 Log.v(LOGTAG, "nativeFocusIsPlugin()=" + nativeFocusIsPlugin()); 6555 Rect r = nativeFocusNodeBounds(); 6556 Log.v(LOGTAG, "nativeFocusNodeBounds()=(" + r.left + ", " + r.top 6557 + ", " + r.right + ", " + r.bottom + ")"); 6558 } 6559 return nativeFocusIsPlugin() 6560 && nativeFocusNodeBounds().contains(contentX, contentY); 6561 } 6562 6563 private boolean shouldForwardTouchEvent() { 6564 if (mFullScreenHolder != null) return true; 6565 if (mBlockWebkitViewMessages) return false; 6566 return mForwardTouchEvents 6567 && !mSelectingText 6568 && mPreventDefault != PREVENT_DEFAULT_IGNORE 6569 && mPreventDefault != PREVENT_DEFAULT_NO; 6570 } 6571 6572 private boolean inFullScreenMode() { 6573 return mFullScreenHolder != null; 6574 } 6575 6576 private void dismissFullScreenMode() { 6577 if (inFullScreenMode()) { 6578 mFullScreenHolder.hide(); 6579 mFullScreenHolder = null; 6580 invalidate(); 6581 } 6582 } 6583 6584 void onPinchToZoomAnimationStart() { 6585 // cancel the single touch handling 6586 cancelTouch(); 6587 onZoomAnimationStart(); 6588 } 6589 6590 void onPinchToZoomAnimationEnd(ScaleGestureDetector detector) { 6591 onZoomAnimationEnd(); 6592 // start a drag, TOUCH_PINCH_DRAG, can't use TOUCH_INIT_MODE as 6593 // it may trigger the unwanted click, can't use TOUCH_DRAG_MODE 6594 // as it may trigger the unwanted fling. 6595 mTouchMode = TOUCH_PINCH_DRAG; 6596 mConfirmMove = true; 6597 startTouch(detector.getFocusX(), detector.getFocusY(), mLastTouchTime); 6598 } 6599 6600 // See if there is a layer at x, y and switch to TOUCH_DRAG_LAYER_MODE if a 6601 // layer is found. 6602 private void startScrollingLayer(float x, float y) { 6603 int contentX = viewToContentX((int) x + mScrollX); 6604 int contentY = viewToContentY((int) y + mScrollY); 6605 mCurrentScrollingLayerId = nativeScrollableLayer(contentX, contentY, 6606 mScrollingLayerRect, mScrollingLayerBounds); 6607 if (mCurrentScrollingLayerId != 0) { 6608 mTouchMode = TOUCH_DRAG_LAYER_MODE; 6609 } 6610 } 6611 6612 // 1/(density * density) used to compute the distance between points. 6613 // Computed in init(). 6614 private float DRAG_LAYER_INVERSE_DENSITY_SQUARED; 6615 6616 // The distance between two points reported in onTouchEvent scaled by the 6617 // density of the screen. 6618 private static final int DRAG_LAYER_FINGER_DISTANCE = 20000; 6619 6620 @Override 6621 public boolean onHoverEvent(MotionEvent event) { 6622 if (mNativeClass == 0) { 6623 return false; 6624 } 6625 WebViewCore.CursorData data = cursorDataNoPosition(); 6626 data.mX = viewToContentX((int) event.getX() + mScrollX); 6627 data.mY = viewToContentY((int) event.getY() + mScrollY); 6628 mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, data); 6629 return true; 6630 } 6631 6632 @Override 6633 public boolean onTouchEvent(MotionEvent ev) { 6634 if (mNativeClass == 0 || (!isClickable() && !isLongClickable())) { 6635 return false; 6636 } 6637 6638 if (DebugFlags.WEB_VIEW) { 6639 Log.v(LOGTAG, ev + " at " + ev.getEventTime() 6640 + " mTouchMode=" + mTouchMode 6641 + " numPointers=" + ev.getPointerCount()); 6642 } 6643 6644 // If WebKit wasn't interested in this multitouch gesture, enqueue 6645 // the event for handling directly rather than making the round trip 6646 // to WebKit and back. 6647 if (ev.getPointerCount() > 1 && mPreventDefault != PREVENT_DEFAULT_NO) { 6648 passMultiTouchToWebKit(ev, mTouchEventQueue.nextTouchSequence()); 6649 } else { 6650 mTouchEventQueue.enqueueTouchEvent(ev); 6651 } 6652 6653 // Since all events are handled asynchronously, we always want the gesture stream. 6654 return true; 6655 } 6656 6657 private float calculateDragAngle(int dx, int dy) { 6658 dx = Math.abs(dx); 6659 dy = Math.abs(dy); 6660 return (float) Math.atan2(dy, dx); 6661 } 6662 6663 /* 6664 * Common code for single touch and multi-touch. 6665 * (x, y) denotes current focus point, which is the touch point for single touch 6666 * and the middle point for multi-touch. 6667 */ 6668 private boolean handleTouchEventCommon(MotionEvent ev, int action, int x, int y) { 6669 long eventTime = ev.getEventTime(); 6670 6671 // Due to the touch screen edge effect, a touch closer to the edge 6672 // always snapped to the edge. As getViewWidth() can be different from 6673 // getWidth() due to the scrollbar, adjusting the point to match 6674 // getViewWidth(). Same applied to the height. 6675 x = Math.min(x, getViewWidth() - 1); 6676 y = Math.min(y, getViewHeightWithTitle() - 1); 6677 6678 int deltaX = mLastTouchX - x; 6679 int deltaY = mLastTouchY - y; 6680 int contentX = viewToContentX(x + mScrollX); 6681 int contentY = viewToContentY(y + mScrollY); 6682 6683 switch (action) { 6684 case MotionEvent.ACTION_DOWN: { 6685 mPreventDefault = PREVENT_DEFAULT_NO; 6686 mConfirmMove = false; 6687 mInitialHitTestResult = null; 6688 if (!mScroller.isFinished()) { 6689 // stop the current scroll animation, but if this is 6690 // the start of a fling, allow it to add to the current 6691 // fling's velocity 6692 mScroller.abortAnimation(); 6693 mTouchMode = TOUCH_DRAG_START_MODE; 6694 mConfirmMove = true; 6695 nativeSetIsScrolling(false); 6696 } else if (mPrivateHandler.hasMessages(RELEASE_SINGLE_TAP)) { 6697 mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP); 6698 if (sDisableNavcache) { 6699 removeTouchHighlight(); 6700 } 6701 if (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare) { 6702 mTouchMode = TOUCH_DOUBLE_TAP_MODE; 6703 } else { 6704 // commit the short press action for the previous tap 6705 doShortPress(); 6706 mTouchMode = TOUCH_INIT_MODE; 6707 mDeferTouchProcess = !mBlockWebkitViewMessages 6708 && (!inFullScreenMode() && mForwardTouchEvents) 6709 ? hitFocusedPlugin(contentX, contentY) 6710 : false; 6711 } 6712 } else { // the normal case 6713 mTouchMode = TOUCH_INIT_MODE; 6714 mDeferTouchProcess = !mBlockWebkitViewMessages 6715 && (!inFullScreenMode() && mForwardTouchEvents) 6716 ? hitFocusedPlugin(contentX, contentY) 6717 : false; 6718 if (!mBlockWebkitViewMessages) { 6719 mWebViewCore.sendMessage( 6720 EventHub.UPDATE_FRAME_CACHE_IF_LOADING); 6721 } 6722 if (sDisableNavcache) { 6723 TouchHighlightData data = new TouchHighlightData(); 6724 data.mX = contentX; 6725 data.mY = contentY; 6726 data.mNativeLayerRect = new Rect(); 6727 data.mNativeLayer = nativeScrollableLayer( 6728 contentX, contentY, data.mNativeLayerRect, null); 6729 data.mSlop = viewToContentDimension(mNavSlop); 6730 mTouchHighlightRegion.setEmpty(); 6731 if (!mBlockWebkitViewMessages) { 6732 mTouchHighlightRequested = System.currentTimeMillis(); 6733 mWebViewCore.sendMessageAtFrontOfQueue( 6734 EventHub.HIT_TEST, data); 6735 } 6736 if (DEBUG_TOUCH_HIGHLIGHT) { 6737 if (getSettings().getNavDump()) { 6738 mTouchHighlightX = x + mScrollX; 6739 mTouchHighlightY = y + mScrollY; 6740 mPrivateHandler.postDelayed(new Runnable() { 6741 @Override 6742 public void run() { 6743 mTouchHighlightX = mTouchHighlightY = 0; 6744 invalidate(); 6745 } 6746 }, TOUCH_HIGHLIGHT_ELAPSE_TIME); 6747 } 6748 } 6749 } 6750 if (mLogEvent && eventTime - mLastTouchUpTime < 1000) { 6751 EventLog.writeEvent(EventLogTags.BROWSER_DOUBLE_TAP_DURATION, 6752 (eventTime - mLastTouchUpTime), eventTime); 6753 } 6754 mSelectionStarted = false; 6755 if (mSelectingText) { 6756 int shiftedY = y - getTitleHeight() + mScrollY; 6757 int shiftedX = x + mScrollX; 6758 if (mSelectHandleCenter != null && mSelectHandleCenter.getBounds() 6759 .contains(shiftedX, shiftedY)) { 6760 mSelectionStarted = true; 6761 mSelectDraggingCursor = mSelectCursorBase; 6762 mPrivateHandler.removeMessages(CLEAR_CARET_HANDLE); 6763 hidePasteButton(); 6764 } else if (mSelectHandleLeft != null 6765 && mSelectHandleLeft.getBounds() 6766 .contains(shiftedX, shiftedY)) { 6767 mSelectionStarted = true; 6768 mSelectDraggingCursor = mSelectCursorBase; 6769 } else if (mSelectHandleRight != null 6770 && mSelectHandleRight.getBounds() 6771 .contains(shiftedX, shiftedY)) { 6772 mSelectionStarted = true; 6773 mSelectDraggingCursor = mSelectCursorExtent; 6774 } else if (mIsCaretSelection) { 6775 selectionDone(); 6776 } 6777 if (mSelectDraggingCursor != null) { 6778 mSelectDraggingOffset.set( 6779 mSelectDraggingCursor.left - contentX, 6780 mSelectDraggingCursor.top - contentY); 6781 } 6782 if (DebugFlags.WEB_VIEW) { 6783 Log.v(LOGTAG, "select=" + contentX + "," + contentY); 6784 } 6785 } 6786 } 6787 // Trigger the link 6788 if (!mSelectingText && (mTouchMode == TOUCH_INIT_MODE 6789 || mTouchMode == TOUCH_DOUBLE_TAP_MODE)) { 6790 mPrivateHandler.sendEmptyMessageDelayed( 6791 SWITCH_TO_SHORTPRESS, TAP_TIMEOUT); 6792 mPrivateHandler.sendEmptyMessageDelayed( 6793 SWITCH_TO_LONGPRESS, LONG_PRESS_TIMEOUT); 6794 if (inFullScreenMode() || mDeferTouchProcess) { 6795 mPreventDefault = PREVENT_DEFAULT_YES; 6796 } else if (!mBlockWebkitViewMessages && mForwardTouchEvents) { 6797 mPreventDefault = PREVENT_DEFAULT_MAYBE_YES; 6798 } else { 6799 mPreventDefault = PREVENT_DEFAULT_NO; 6800 } 6801 // pass the touch events from UI thread to WebCore thread 6802 if (shouldForwardTouchEvent()) { 6803 TouchEventData ted = new TouchEventData(); 6804 ted.mAction = action; 6805 ted.mIds = new int[1]; 6806 ted.mIds[0] = ev.getPointerId(0); 6807 ted.mPoints = new Point[1]; 6808 ted.mPoints[0] = new Point(contentX, contentY); 6809 ted.mPointsInView = new Point[1]; 6810 ted.mPointsInView[0] = new Point(x, y); 6811 ted.mMetaState = ev.getMetaState(); 6812 ted.mReprocess = mDeferTouchProcess; 6813 ted.mNativeLayer = nativeScrollableLayer( 6814 contentX, contentY, ted.mNativeLayerRect, null); 6815 ted.mSequence = mTouchEventQueue.nextTouchSequence(); 6816 mTouchEventQueue.preQueueTouchEventData(ted); 6817 mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); 6818 if (mDeferTouchProcess) { 6819 // still needs to set them for compute deltaX/Y 6820 mLastTouchX = x; 6821 mLastTouchY = y; 6822 break; 6823 } 6824 if (!inFullScreenMode()) { 6825 mPrivateHandler.removeMessages(PREVENT_DEFAULT_TIMEOUT); 6826 mPrivateHandler.sendMessageDelayed(mPrivateHandler 6827 .obtainMessage(PREVENT_DEFAULT_TIMEOUT, 6828 action, 0), TAP_TIMEOUT); 6829 } 6830 } 6831 } 6832 startTouch(x, y, eventTime); 6833 break; 6834 } 6835 case MotionEvent.ACTION_MOVE: { 6836 boolean firstMove = false; 6837 if (!mConfirmMove && (deltaX * deltaX + deltaY * deltaY) 6838 >= mTouchSlopSquare) { 6839 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); 6840 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); 6841 mConfirmMove = true; 6842 firstMove = true; 6843 if (mTouchMode == TOUCH_DOUBLE_TAP_MODE) { 6844 mTouchMode = TOUCH_INIT_MODE; 6845 } 6846 if (sDisableNavcache) { 6847 removeTouchHighlight(); 6848 } 6849 } 6850 if (mSelectingText && mSelectionStarted) { 6851 if (DebugFlags.WEB_VIEW) { 6852 Log.v(LOGTAG, "extend=" + contentX + "," + contentY); 6853 } 6854 ViewParent parent = getParent(); 6855 if (parent != null) { 6856 parent.requestDisallowInterceptTouchEvent(true); 6857 } 6858 if (deltaX != 0 || deltaY != 0) { 6859 mSelectDraggingCursor.offsetTo( 6860 contentX + mSelectDraggingOffset.x, 6861 contentY + mSelectDraggingOffset.y); 6862 updateWebkitSelection(); 6863 mLastTouchX = x; 6864 mLastTouchY = y; 6865 invalidate(); 6866 } 6867 break; 6868 } 6869 6870 // pass the touch events from UI thread to WebCore thread 6871 if (shouldForwardTouchEvent() && mConfirmMove && (firstMove 6872 || eventTime - mLastSentTouchTime > mCurrentTouchInterval)) { 6873 TouchEventData ted = new TouchEventData(); 6874 ted.mAction = action; 6875 ted.mIds = new int[1]; 6876 ted.mIds[0] = ev.getPointerId(0); 6877 ted.mPoints = new Point[1]; 6878 ted.mPoints[0] = new Point(contentX, contentY); 6879 ted.mPointsInView = new Point[1]; 6880 ted.mPointsInView[0] = new Point(x, y); 6881 ted.mMetaState = ev.getMetaState(); 6882 ted.mReprocess = mDeferTouchProcess; 6883 ted.mNativeLayer = mCurrentScrollingLayerId; 6884 ted.mNativeLayerRect.set(mScrollingLayerRect); 6885 ted.mSequence = mTouchEventQueue.nextTouchSequence(); 6886 mTouchEventQueue.preQueueTouchEventData(ted); 6887 mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); 6888 mLastSentTouchTime = eventTime; 6889 if (mDeferTouchProcess) { 6890 break; 6891 } 6892 if (firstMove && !inFullScreenMode()) { 6893 mPrivateHandler.sendMessageDelayed(mPrivateHandler 6894 .obtainMessage(PREVENT_DEFAULT_TIMEOUT, 6895 action, 0), TAP_TIMEOUT); 6896 } 6897 } 6898 if (mTouchMode == TOUCH_DONE_MODE 6899 || mPreventDefault == PREVENT_DEFAULT_YES) { 6900 // no dragging during scroll zoom animation, or when prevent 6901 // default is yes 6902 break; 6903 } 6904 if (mVelocityTracker == null) { 6905 Log.e(LOGTAG, "Got null mVelocityTracker when " 6906 + "mPreventDefault = " + mPreventDefault 6907 + " mDeferTouchProcess = " + mDeferTouchProcess 6908 + " mTouchMode = " + mTouchMode); 6909 } else { 6910 mVelocityTracker.addMovement(ev); 6911 } 6912 6913 if (mTouchMode != TOUCH_DRAG_MODE && 6914 mTouchMode != TOUCH_DRAG_LAYER_MODE) { 6915 6916 if (!mConfirmMove) { 6917 break; 6918 } 6919 6920 if (mPreventDefault == PREVENT_DEFAULT_MAYBE_YES 6921 || mPreventDefault == PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN) { 6922 // track mLastTouchTime as we may need to do fling at 6923 // ACTION_UP 6924 mLastTouchTime = eventTime; 6925 break; 6926 } 6927 6928 // Only lock dragging to one axis if we don't have a scale in progress. 6929 // Scaling implies free-roaming movement. Note this is only ever a question 6930 // if mZoomManager.supportsPanDuringZoom() is true. 6931 final ScaleGestureDetector detector = 6932 mZoomManager.getMultiTouchGestureDetector(); 6933 mAverageAngle = calculateDragAngle(deltaX, deltaY); 6934 if (detector == null || !detector.isInProgress()) { 6935 // if it starts nearly horizontal or vertical, enforce it 6936 if (mAverageAngle < HSLOPE_TO_START_SNAP) { 6937 mSnapScrollMode = SNAP_X; 6938 mSnapPositive = deltaX > 0; 6939 mAverageAngle = ANGLE_HORIZ; 6940 } else if (mAverageAngle > VSLOPE_TO_START_SNAP) { 6941 mSnapScrollMode = SNAP_Y; 6942 mSnapPositive = deltaY > 0; 6943 mAverageAngle = ANGLE_VERT; 6944 } 6945 } 6946 6947 mTouchMode = TOUCH_DRAG_MODE; 6948 mLastTouchX = x; 6949 mLastTouchY = y; 6950 deltaX = 0; 6951 deltaY = 0; 6952 6953 startScrollingLayer(x, y); 6954 startDrag(); 6955 } 6956 6957 // do pan 6958 boolean done = false; 6959 boolean keepScrollBarsVisible = false; 6960 if (deltaX == 0 && deltaY == 0) { 6961 keepScrollBarsVisible = done = true; 6962 } else { 6963 mAverageAngle += 6964 (calculateDragAngle(deltaX, deltaY) - mAverageAngle) 6965 / MMA_WEIGHT_N; 6966 if (mSnapScrollMode != SNAP_NONE) { 6967 if (mSnapScrollMode == SNAP_Y) { 6968 // radical change means getting out of snap mode 6969 if (mAverageAngle < VSLOPE_TO_BREAK_SNAP) { 6970 mSnapScrollMode = SNAP_NONE; 6971 } 6972 } 6973 if (mSnapScrollMode == SNAP_X) { 6974 // radical change means getting out of snap mode 6975 if (mAverageAngle > HSLOPE_TO_BREAK_SNAP) { 6976 mSnapScrollMode = SNAP_NONE; 6977 } 6978 } 6979 } else { 6980 if (mAverageAngle < HSLOPE_TO_START_SNAP) { 6981 mSnapScrollMode = SNAP_X; 6982 mSnapPositive = deltaX > 0; 6983 mAverageAngle = (mAverageAngle + ANGLE_HORIZ) / 2; 6984 } else if (mAverageAngle > VSLOPE_TO_START_SNAP) { 6985 mSnapScrollMode = SNAP_Y; 6986 mSnapPositive = deltaY > 0; 6987 mAverageAngle = (mAverageAngle + ANGLE_VERT) / 2; 6988 } 6989 } 6990 if (mSnapScrollMode != SNAP_NONE) { 6991 if ((mSnapScrollMode & SNAP_X) == SNAP_X) { 6992 deltaY = 0; 6993 } else { 6994 deltaX = 0; 6995 } 6996 } 6997 mLastTouchX = x; 6998 mLastTouchY = y; 6999 7000 if (deltaX * deltaX + deltaY * deltaY > mTouchSlopSquare) { 7001 mHeldMotionless = MOTIONLESS_FALSE; 7002 nativeSetIsScrolling(true); 7003 } else { 7004 mHeldMotionless = MOTIONLESS_TRUE; 7005 nativeSetIsScrolling(false); 7006 keepScrollBarsVisible = true; 7007 } 7008 7009 mLastTouchTime = eventTime; 7010 } 7011 7012 doDrag(deltaX, deltaY); 7013 7014 // Turn off scrollbars when dragging a layer. 7015 if (keepScrollBarsVisible && 7016 mTouchMode != TOUCH_DRAG_LAYER_MODE) { 7017 if (mHeldMotionless != MOTIONLESS_TRUE) { 7018 mHeldMotionless = MOTIONLESS_TRUE; 7019 invalidate(); 7020 } 7021 // keep the scrollbar on the screen even there is no scroll 7022 awakenScrollBars(ViewConfiguration.getScrollDefaultDelay(), 7023 false); 7024 // Post a message so that we'll keep them alive while we're not scrolling. 7025 mPrivateHandler.sendMessageDelayed(mPrivateHandler 7026 .obtainMessage(AWAKEN_SCROLL_BARS), 7027 ViewConfiguration.getScrollDefaultDelay()); 7028 // return false to indicate that we can't pan out of the 7029 // view space 7030 return !done; 7031 } else { 7032 mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS); 7033 } 7034 break; 7035 } 7036 case MotionEvent.ACTION_UP: { 7037 if (!isFocused()) requestFocus(); 7038 // pass the touch events from UI thread to WebCore thread 7039 if (shouldForwardTouchEvent()) { 7040 TouchEventData ted = new TouchEventData(); 7041 ted.mIds = new int[1]; 7042 ted.mIds[0] = ev.getPointerId(0); 7043 ted.mAction = action; 7044 ted.mPoints = new Point[1]; 7045 ted.mPoints[0] = new Point(contentX, contentY); 7046 ted.mPointsInView = new Point[1]; 7047 ted.mPointsInView[0] = new Point(x, y); 7048 ted.mMetaState = ev.getMetaState(); 7049 ted.mReprocess = mDeferTouchProcess; 7050 ted.mNativeLayer = mCurrentScrollingLayerId; 7051 ted.mNativeLayerRect.set(mScrollingLayerRect); 7052 ted.mSequence = mTouchEventQueue.nextTouchSequence(); 7053 mTouchEventQueue.preQueueTouchEventData(ted); 7054 mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); 7055 } 7056 mLastTouchUpTime = eventTime; 7057 if (mSentAutoScrollMessage) { 7058 mAutoScrollX = mAutoScrollY = 0; 7059 } 7060 switch (mTouchMode) { 7061 case TOUCH_DOUBLE_TAP_MODE: // double tap 7062 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); 7063 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); 7064 if (inFullScreenMode() || mDeferTouchProcess) { 7065 TouchEventData ted = new TouchEventData(); 7066 ted.mIds = new int[1]; 7067 ted.mIds[0] = ev.getPointerId(0); 7068 ted.mAction = WebViewCore.ACTION_DOUBLETAP; 7069 ted.mPoints = new Point[1]; 7070 ted.mPoints[0] = new Point(contentX, contentY); 7071 ted.mPointsInView = new Point[1]; 7072 ted.mPointsInView[0] = new Point(x, y); 7073 ted.mMetaState = ev.getMetaState(); 7074 ted.mReprocess = mDeferTouchProcess; 7075 ted.mNativeLayer = nativeScrollableLayer( 7076 contentX, contentY, 7077 ted.mNativeLayerRect, null); 7078 ted.mSequence = mTouchEventQueue.nextTouchSequence(); 7079 mTouchEventQueue.preQueueTouchEventData(ted); 7080 mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); 7081 } else if (mPreventDefault != PREVENT_DEFAULT_YES){ 7082 mZoomManager.handleDoubleTap(mLastTouchX, mLastTouchY); 7083 mTouchMode = TOUCH_DONE_MODE; 7084 } 7085 break; 7086 case TOUCH_INIT_MODE: // tap 7087 case TOUCH_SHORTPRESS_START_MODE: 7088 case TOUCH_SHORTPRESS_MODE: 7089 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); 7090 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); 7091 if (mConfirmMove) { 7092 Log.w(LOGTAG, "Miss a drag as we are waiting for" + 7093 " WebCore's response for touch down."); 7094 if (mPreventDefault != PREVENT_DEFAULT_YES 7095 && (computeMaxScrollX() > 0 7096 || computeMaxScrollY() > 0)) { 7097 // If the user has performed a very quick touch 7098 // sequence it is possible that we may get here 7099 // before WebCore has had a chance to process the events. 7100 // In this case, any call to preventDefault in the 7101 // JS touch handler will not have been executed yet. 7102 // Hence we will see both the UI (now) and WebCore 7103 // (when context switches) handling the event, 7104 // regardless of whether the web developer actually 7105 // doeses preventDefault in their touch handler. This 7106 // is the nature of our asynchronous touch model. 7107 7108 // we will not rewrite drag code here, but we 7109 // will try fling if it applies. 7110 WebViewCore.reducePriority(); 7111 // to get better performance, pause updating the 7112 // picture 7113 WebViewCore.pauseUpdatePicture(mWebViewCore); 7114 // fall through to TOUCH_DRAG_MODE 7115 } else { 7116 // WebKit may consume the touch event and modify 7117 // DOM. drawContentPicture() will be called with 7118 // animateSroll as true for better performance. 7119 // Force redraw in high-quality. 7120 invalidate(); 7121 break; 7122 } 7123 } else { 7124 if (mSelectingText) { 7125 // tapping on selection or controls does nothing 7126 if (!mSelectionStarted) { 7127 selectionDone(); 7128 } 7129 break; 7130 } 7131 // only trigger double tap if the WebView is 7132 // scalable 7133 if (mTouchMode == TOUCH_INIT_MODE 7134 && (canZoomIn() || canZoomOut())) { 7135 mPrivateHandler.sendEmptyMessageDelayed( 7136 RELEASE_SINGLE_TAP, ViewConfiguration 7137 .getDoubleTapTimeout()); 7138 } else { 7139 doShortPress(); 7140 } 7141 break; 7142 } 7143 case TOUCH_DRAG_MODE: 7144 case TOUCH_DRAG_LAYER_MODE: 7145 mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS); 7146 mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS); 7147 // if the user waits a while w/o moving before the 7148 // up, we don't want to do a fling 7149 if (eventTime - mLastTouchTime <= MIN_FLING_TIME) { 7150 if (mVelocityTracker == null) { 7151 Log.e(LOGTAG, "Got null mVelocityTracker when " 7152 + "mPreventDefault = " 7153 + mPreventDefault 7154 + " mDeferTouchProcess = " 7155 + mDeferTouchProcess); 7156 } else { 7157 mVelocityTracker.addMovement(ev); 7158 } 7159 // set to MOTIONLESS_IGNORE so that it won't keep 7160 // removing and sending message in 7161 // drawCoreAndCursorRing() 7162 mHeldMotionless = MOTIONLESS_IGNORE; 7163 doFling(); 7164 break; 7165 } else { 7166 if (mScroller.springBack(mScrollX, mScrollY, 0, 7167 computeMaxScrollX(), 0, 7168 computeMaxScrollY())) { 7169 invalidate(); 7170 } 7171 } 7172 // redraw in high-quality, as we're done dragging 7173 mHeldMotionless = MOTIONLESS_TRUE; 7174 invalidate(); 7175 // fall through 7176 case TOUCH_DRAG_START_MODE: 7177 // TOUCH_DRAG_START_MODE should not happen for the real 7178 // device as we almost certain will get a MOVE. But this 7179 // is possible on emulator. 7180 mLastVelocity = 0; 7181 WebViewCore.resumePriority(); 7182 if (!mSelectingText) { 7183 WebViewCore.resumeUpdatePicture(mWebViewCore); 7184 } 7185 break; 7186 } 7187 stopTouch(); 7188 break; 7189 } 7190 case MotionEvent.ACTION_CANCEL: { 7191 if (mTouchMode == TOUCH_DRAG_MODE) { 7192 mScroller.springBack(mScrollX, mScrollY, 0, 7193 computeMaxScrollX(), 0, computeMaxScrollY()); 7194 invalidate(); 7195 } 7196 cancelWebCoreTouchEvent(contentX, contentY, false); 7197 cancelTouch(); 7198 break; 7199 } 7200 } 7201 return true; 7202 } 7203 7204 private void passMultiTouchToWebKit(MotionEvent ev, long sequence) { 7205 TouchEventData ted = new TouchEventData(); 7206 ted.mAction = ev.getActionMasked(); 7207 final int count = ev.getPointerCount(); 7208 ted.mIds = new int[count]; 7209 ted.mPoints = new Point[count]; 7210 ted.mPointsInView = new Point[count]; 7211 for (int c = 0; c < count; c++) { 7212 ted.mIds[c] = ev.getPointerId(c); 7213 int x = viewToContentX((int) ev.getX(c) + mScrollX); 7214 int y = viewToContentY((int) ev.getY(c) + mScrollY); 7215 ted.mPoints[c] = new Point(x, y); 7216 ted.mPointsInView[c] = new Point((int) ev.getX(c), (int) ev.getY(c)); 7217 } 7218 if (ted.mAction == MotionEvent.ACTION_POINTER_DOWN 7219 || ted.mAction == MotionEvent.ACTION_POINTER_UP) { 7220 ted.mActionIndex = ev.getActionIndex(); 7221 } 7222 ted.mMetaState = ev.getMetaState(); 7223 ted.mReprocess = true; 7224 ted.mMotionEvent = MotionEvent.obtain(ev); 7225 ted.mSequence = sequence; 7226 mTouchEventQueue.preQueueTouchEventData(ted); 7227 mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); 7228 cancelLongPress(); 7229 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); 7230 } 7231 7232 void handleMultiTouchInWebView(MotionEvent ev) { 7233 if (DebugFlags.WEB_VIEW) { 7234 Log.v(LOGTAG, "multi-touch: " + ev + " at " + ev.getEventTime() 7235 + " mTouchMode=" + mTouchMode 7236 + " numPointers=" + ev.getPointerCount() 7237 + " scrolloffset=(" + mScrollX + "," + mScrollY + ")"); 7238 } 7239 7240 final ScaleGestureDetector detector = 7241 mZoomManager.getMultiTouchGestureDetector(); 7242 7243 // A few apps use WebView but don't instantiate gesture detector. 7244 // We don't need to support multi touch for them. 7245 if (detector == null) return; 7246 7247 float x = ev.getX(); 7248 float y = ev.getY(); 7249 7250 if (mPreventDefault != PREVENT_DEFAULT_YES) { 7251 detector.onTouchEvent(ev); 7252 7253 if (detector.isInProgress()) { 7254 if (DebugFlags.WEB_VIEW) { 7255 Log.v(LOGTAG, "detector is in progress"); 7256 } 7257 mLastTouchTime = ev.getEventTime(); 7258 x = detector.getFocusX(); 7259 y = detector.getFocusY(); 7260 7261 cancelLongPress(); 7262 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); 7263 if (!mZoomManager.supportsPanDuringZoom()) { 7264 return; 7265 } 7266 mTouchMode = TOUCH_DRAG_MODE; 7267 if (mVelocityTracker == null) { 7268 mVelocityTracker = VelocityTracker.obtain(); 7269 } 7270 } 7271 } 7272 7273 int action = ev.getActionMasked(); 7274 if (action == MotionEvent.ACTION_POINTER_DOWN) { 7275 cancelTouch(); 7276 action = MotionEvent.ACTION_DOWN; 7277 } else if (action == MotionEvent.ACTION_POINTER_UP && ev.getPointerCount() >= 2) { 7278 // set mLastTouchX/Y to the remaining points for multi-touch. 7279 mLastTouchX = Math.round(x); 7280 mLastTouchY = Math.round(y); 7281 } else if (action == MotionEvent.ACTION_MOVE) { 7282 // negative x or y indicate it is on the edge, skip it. 7283 if (x < 0 || y < 0) { 7284 return; 7285 } 7286 } 7287 7288 handleTouchEventCommon(ev, action, Math.round(x), Math.round(y)); 7289 } 7290 7291 private void cancelWebCoreTouchEvent(int x, int y, boolean removeEvents) { 7292 if (shouldForwardTouchEvent()) { 7293 if (removeEvents) { 7294 mWebViewCore.removeMessages(EventHub.TOUCH_EVENT); 7295 } 7296 TouchEventData ted = new TouchEventData(); 7297 ted.mIds = new int[1]; 7298 ted.mIds[0] = 0; 7299 ted.mPoints = new Point[1]; 7300 ted.mPoints[0] = new Point(x, y); 7301 ted.mPointsInView = new Point[1]; 7302 int viewX = contentToViewX(x) - mScrollX; 7303 int viewY = contentToViewY(y) - mScrollY; 7304 ted.mPointsInView[0] = new Point(viewX, viewY); 7305 ted.mAction = MotionEvent.ACTION_CANCEL; 7306 ted.mNativeLayer = nativeScrollableLayer( 7307 x, y, ted.mNativeLayerRect, null); 7308 ted.mSequence = mTouchEventQueue.nextTouchSequence(); 7309 mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); 7310 mPreventDefault = PREVENT_DEFAULT_IGNORE; 7311 7312 if (removeEvents) { 7313 // Mark this after sending the message above; we should 7314 // be willing to ignore the cancel event that we just sent. 7315 mTouchEventQueue.ignoreCurrentlyMissingEvents(); 7316 } 7317 } 7318 } 7319 7320 private void startTouch(float x, float y, long eventTime) { 7321 // Remember where the motion event started 7322 mStartTouchX = mLastTouchX = Math.round(x); 7323 mStartTouchY = mLastTouchY = Math.round(y); 7324 mLastTouchTime = eventTime; 7325 mVelocityTracker = VelocityTracker.obtain(); 7326 mSnapScrollMode = SNAP_NONE; 7327 mPrivateHandler.sendEmptyMessageDelayed(UPDATE_SELECTION, 7328 ViewConfiguration.getTapTimeout()); 7329 } 7330 7331 private void startDrag() { 7332 WebViewCore.reducePriority(); 7333 // to get better performance, pause updating the picture 7334 WebViewCore.pauseUpdatePicture(mWebViewCore); 7335 nativeSetIsScrolling(true); 7336 7337 if (!mDragFromTextInput) { 7338 nativeHideCursor(); 7339 } 7340 7341 if (mHorizontalScrollBarMode != SCROLLBAR_ALWAYSOFF 7342 || mVerticalScrollBarMode != SCROLLBAR_ALWAYSOFF) { 7343 mZoomManager.invokeZoomPicker(); 7344 } 7345 } 7346 7347 private void doDrag(int deltaX, int deltaY) { 7348 if ((deltaX | deltaY) != 0) { 7349 int oldX = mScrollX; 7350 int oldY = mScrollY; 7351 int rangeX = computeMaxScrollX(); 7352 int rangeY = computeMaxScrollY(); 7353 // Check for the original scrolling layer in case we change 7354 // directions. mTouchMode might be TOUCH_DRAG_MODE if we have 7355 // reached the edge of a layer but mScrollingLayer will be non-zero 7356 // if we initiated the drag on a layer. 7357 if (mCurrentScrollingLayerId != 0) { 7358 final int contentX = viewToContentDimension(deltaX); 7359 final int contentY = viewToContentDimension(deltaY); 7360 7361 // Check the scrolling bounds to see if we will actually do any 7362 // scrolling. The rectangle is in document coordinates. 7363 final int maxX = mScrollingLayerRect.right; 7364 final int maxY = mScrollingLayerRect.bottom; 7365 final int resultX = Math.max(0, 7366 Math.min(mScrollingLayerRect.left + contentX, maxX)); 7367 final int resultY = Math.max(0, 7368 Math.min(mScrollingLayerRect.top + contentY, maxY)); 7369 7370 if (resultX != mScrollingLayerRect.left || 7371 resultY != mScrollingLayerRect.top) { 7372 // In case we switched to dragging the page. 7373 mTouchMode = TOUCH_DRAG_LAYER_MODE; 7374 deltaX = contentX; 7375 deltaY = contentY; 7376 oldX = mScrollingLayerRect.left; 7377 oldY = mScrollingLayerRect.top; 7378 rangeX = maxX; 7379 rangeY = maxY; 7380 } else { 7381 // Scroll the main page if we are not going to scroll the 7382 // layer. This does not reset mScrollingLayer in case the 7383 // user changes directions and the layer can scroll the 7384 // other way. 7385 mTouchMode = TOUCH_DRAG_MODE; 7386 } 7387 } 7388 7389 if (mOverScrollGlow != null) { 7390 mOverScrollGlow.setOverScrollDeltas(deltaX, deltaY); 7391 } 7392 7393 overScrollBy(deltaX, deltaY, oldX, oldY, 7394 rangeX, rangeY, 7395 mOverscrollDistance, mOverscrollDistance, true); 7396 if (mOverScrollGlow != null && mOverScrollGlow.isAnimating()) { 7397 invalidate(); 7398 } 7399 } 7400 mZoomManager.keepZoomPickerVisible(); 7401 } 7402 7403 private void stopTouch() { 7404 if (mScroller.isFinished() && !mSelectingText 7405 && (mTouchMode == TOUCH_DRAG_MODE || mTouchMode == TOUCH_DRAG_LAYER_MODE)) { 7406 WebViewCore.resumePriority(); 7407 WebViewCore.resumeUpdatePicture(mWebViewCore); 7408 nativeSetIsScrolling(false); 7409 } 7410 7411 // we also use mVelocityTracker == null to tell us that we are 7412 // not "moving around", so we can take the slower/prettier 7413 // mode in the drawing code 7414 if (mVelocityTracker != null) { 7415 mVelocityTracker.recycle(); 7416 mVelocityTracker = null; 7417 } 7418 7419 // Release any pulled glows 7420 if (mOverScrollGlow != null) { 7421 mOverScrollGlow.releaseAll(); 7422 } 7423 7424 if (mSelectingText) { 7425 mSelectionStarted = false; 7426 syncSelectionCursors(); 7427 if (mIsCaretSelection) { 7428 resetCaretTimer(); 7429 showPasteWindow(); 7430 } 7431 invalidate(); 7432 } 7433 } 7434 7435 private void cancelTouch() { 7436 // we also use mVelocityTracker == null to tell us that we are 7437 // not "moving around", so we can take the slower/prettier 7438 // mode in the drawing code 7439 if (mVelocityTracker != null) { 7440 mVelocityTracker.recycle(); 7441 mVelocityTracker = null; 7442 } 7443 7444 if ((mTouchMode == TOUCH_DRAG_MODE 7445 || mTouchMode == TOUCH_DRAG_LAYER_MODE) && !mSelectingText) { 7446 WebViewCore.resumePriority(); 7447 WebViewCore.resumeUpdatePicture(mWebViewCore); 7448 nativeSetIsScrolling(false); 7449 } 7450 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); 7451 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); 7452 mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS); 7453 mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS); 7454 if (sDisableNavcache) { 7455 removeTouchHighlight(); 7456 } 7457 mHeldMotionless = MOTIONLESS_TRUE; 7458 mTouchMode = TOUCH_DONE_MODE; 7459 nativeHideCursor(); 7460 } 7461 7462 @Override 7463 public boolean onGenericMotionEvent(MotionEvent event) { 7464 if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { 7465 switch (event.getAction()) { 7466 case MotionEvent.ACTION_SCROLL: { 7467 final float vscroll; 7468 final float hscroll; 7469 if ((event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0) { 7470 vscroll = 0; 7471 hscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL); 7472 } else { 7473 vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL); 7474 hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL); 7475 } 7476 if (hscroll != 0 || vscroll != 0) { 7477 final int vdelta = (int) (vscroll * getVerticalScrollFactor()); 7478 final int hdelta = (int) (hscroll * getHorizontalScrollFactor()); 7479 if (pinScrollBy(hdelta, vdelta, false, 0)) { 7480 return true; 7481 } 7482 } 7483 } 7484 } 7485 } 7486 return super.onGenericMotionEvent(event); 7487 } 7488 7489 private long mTrackballFirstTime = 0; 7490 private long mTrackballLastTime = 0; 7491 private float mTrackballRemainsX = 0.0f; 7492 private float mTrackballRemainsY = 0.0f; 7493 private int mTrackballXMove = 0; 7494 private int mTrackballYMove = 0; 7495 private boolean mSelectingText = false; 7496 private boolean mSelectionStarted = false; 7497 private static final int TRACKBALL_KEY_TIMEOUT = 1000; 7498 private static final int TRACKBALL_TIMEOUT = 200; 7499 private static final int TRACKBALL_WAIT = 100; 7500 private static final int TRACKBALL_SCALE = 400; 7501 private static final int TRACKBALL_SCROLL_COUNT = 5; 7502 private static final int TRACKBALL_MOVE_COUNT = 10; 7503 private static final int TRACKBALL_MULTIPLIER = 3; 7504 private static final int SELECT_CURSOR_OFFSET = 16; 7505 private static final int SELECT_SCROLL = 5; 7506 private int mSelectX = 0; 7507 private int mSelectY = 0; 7508 private boolean mFocusSizeChanged = false; 7509 private boolean mTrackballDown = false; 7510 private long mTrackballUpTime = 0; 7511 private long mLastCursorTime = 0; 7512 private Rect mLastCursorBounds; 7513 7514 // Set by default; BrowserActivity clears to interpret trackball data 7515 // directly for movement. Currently, the framework only passes 7516 // arrow key events, not trackball events, from one child to the next 7517 private boolean mMapTrackballToArrowKeys = true; 7518 7519 private DrawData mDelaySetPicture; 7520 private DrawData mLoadedPicture; 7521 7522 public void setMapTrackballToArrowKeys(boolean setMap) { 7523 checkThread(); 7524 mMapTrackballToArrowKeys = setMap; 7525 } 7526 7527 void resetTrackballTime() { 7528 mTrackballLastTime = 0; 7529 } 7530 7531 @Override 7532 public boolean onTrackballEvent(MotionEvent ev) { 7533 long time = ev.getEventTime(); 7534 if ((ev.getMetaState() & KeyEvent.META_ALT_ON) != 0) { 7535 if (ev.getY() > 0) pageDown(true); 7536 if (ev.getY() < 0) pageUp(true); 7537 return true; 7538 } 7539 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 7540 if (mSelectingText) { 7541 return true; // discard press if copy in progress 7542 } 7543 mTrackballDown = true; 7544 if (mNativeClass == 0) { 7545 return false; 7546 } 7547 if (time - mLastCursorTime <= TRACKBALL_TIMEOUT 7548 && !mLastCursorBounds.equals(nativeGetCursorRingBounds())) { 7549 nativeSelectBestAt(mLastCursorBounds); 7550 } 7551 if (DebugFlags.WEB_VIEW) { 7552 Log.v(LOGTAG, "onTrackballEvent down ev=" + ev 7553 + " time=" + time 7554 + " mLastCursorTime=" + mLastCursorTime); 7555 } 7556 if (isInTouchMode()) requestFocusFromTouch(); 7557 return false; // let common code in onKeyDown at it 7558 } 7559 if (ev.getAction() == MotionEvent.ACTION_UP) { 7560 // LONG_PRESS_CENTER is set in common onKeyDown 7561 mPrivateHandler.removeMessages(LONG_PRESS_CENTER); 7562 mTrackballDown = false; 7563 mTrackballUpTime = time; 7564 if (mSelectingText) { 7565 copySelection(); 7566 selectionDone(); 7567 return true; // discard press if copy in progress 7568 } 7569 if (DebugFlags.WEB_VIEW) { 7570 Log.v(LOGTAG, "onTrackballEvent up ev=" + ev 7571 + " time=" + time 7572 ); 7573 } 7574 return false; // let common code in onKeyUp at it 7575 } 7576 if ((mMapTrackballToArrowKeys && (ev.getMetaState() & KeyEvent.META_SHIFT_ON) == 0) || 7577 AccessibilityManager.getInstance(mContext).isEnabled()) { 7578 if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent gmail quit"); 7579 return false; 7580 } 7581 if (mTrackballDown) { 7582 if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent down quit"); 7583 return true; // discard move if trackball is down 7584 } 7585 if (time - mTrackballUpTime < TRACKBALL_TIMEOUT) { 7586 if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent up timeout quit"); 7587 return true; 7588 } 7589 // TODO: alternatively we can do panning as touch does 7590 switchOutDrawHistory(); 7591 if (time - mTrackballLastTime > TRACKBALL_TIMEOUT) { 7592 if (DebugFlags.WEB_VIEW) { 7593 Log.v(LOGTAG, "onTrackballEvent time=" 7594 + time + " last=" + mTrackballLastTime); 7595 } 7596 mTrackballFirstTime = time; 7597 mTrackballXMove = mTrackballYMove = 0; 7598 } 7599 mTrackballLastTime = time; 7600 if (DebugFlags.WEB_VIEW) { 7601 Log.v(LOGTAG, "onTrackballEvent ev=" + ev + " time=" + time); 7602 } 7603 mTrackballRemainsX += ev.getX(); 7604 mTrackballRemainsY += ev.getY(); 7605 doTrackball(time, ev.getMetaState()); 7606 return true; 7607 } 7608 7609 private int scaleTrackballX(float xRate, int width) { 7610 int xMove = (int) (xRate / TRACKBALL_SCALE * width); 7611 int nextXMove = xMove; 7612 if (xMove > 0) { 7613 if (xMove > mTrackballXMove) { 7614 xMove -= mTrackballXMove; 7615 } 7616 } else if (xMove < mTrackballXMove) { 7617 xMove -= mTrackballXMove; 7618 } 7619 mTrackballXMove = nextXMove; 7620 return xMove; 7621 } 7622 7623 private int scaleTrackballY(float yRate, int height) { 7624 int yMove = (int) (yRate / TRACKBALL_SCALE * height); 7625 int nextYMove = yMove; 7626 if (yMove > 0) { 7627 if (yMove > mTrackballYMove) { 7628 yMove -= mTrackballYMove; 7629 } 7630 } else if (yMove < mTrackballYMove) { 7631 yMove -= mTrackballYMove; 7632 } 7633 mTrackballYMove = nextYMove; 7634 return yMove; 7635 } 7636 7637 private int keyCodeToSoundsEffect(int keyCode) { 7638 switch(keyCode) { 7639 case KeyEvent.KEYCODE_DPAD_UP: 7640 return SoundEffectConstants.NAVIGATION_UP; 7641 case KeyEvent.KEYCODE_DPAD_RIGHT: 7642 return SoundEffectConstants.NAVIGATION_RIGHT; 7643 case KeyEvent.KEYCODE_DPAD_DOWN: 7644 return SoundEffectConstants.NAVIGATION_DOWN; 7645 case KeyEvent.KEYCODE_DPAD_LEFT: 7646 return SoundEffectConstants.NAVIGATION_LEFT; 7647 } 7648 throw new IllegalArgumentException("keyCode must be one of " + 7649 "{KEYCODE_DPAD_UP, KEYCODE_DPAD_RIGHT, KEYCODE_DPAD_DOWN, " + 7650 "KEYCODE_DPAD_LEFT}."); 7651 } 7652 7653 private void doTrackball(long time, int metaState) { 7654 int elapsed = (int) (mTrackballLastTime - mTrackballFirstTime); 7655 if (elapsed == 0) { 7656 elapsed = TRACKBALL_TIMEOUT; 7657 } 7658 float xRate = mTrackballRemainsX * 1000 / elapsed; 7659 float yRate = mTrackballRemainsY * 1000 / elapsed; 7660 int viewWidth = getViewWidth(); 7661 int viewHeight = getViewHeight(); 7662 float ax = Math.abs(xRate); 7663 float ay = Math.abs(yRate); 7664 float maxA = Math.max(ax, ay); 7665 if (DebugFlags.WEB_VIEW) { 7666 Log.v(LOGTAG, "doTrackball elapsed=" + elapsed 7667 + " xRate=" + xRate 7668 + " yRate=" + yRate 7669 + " mTrackballRemainsX=" + mTrackballRemainsX 7670 + " mTrackballRemainsY=" + mTrackballRemainsY); 7671 } 7672 int width = mContentWidth - viewWidth; 7673 int height = mContentHeight - viewHeight; 7674 if (width < 0) width = 0; 7675 if (height < 0) height = 0; 7676 ax = Math.abs(mTrackballRemainsX * TRACKBALL_MULTIPLIER); 7677 ay = Math.abs(mTrackballRemainsY * TRACKBALL_MULTIPLIER); 7678 maxA = Math.max(ax, ay); 7679 int count = Math.max(0, (int) maxA); 7680 int oldScrollX = mScrollX; 7681 int oldScrollY = mScrollY; 7682 if (count > 0) { 7683 int selectKeyCode = ax < ay ? mTrackballRemainsY < 0 ? 7684 KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_DPAD_DOWN : 7685 mTrackballRemainsX < 0 ? KeyEvent.KEYCODE_DPAD_LEFT : 7686 KeyEvent.KEYCODE_DPAD_RIGHT; 7687 count = Math.min(count, TRACKBALL_MOVE_COUNT); 7688 if (DebugFlags.WEB_VIEW) { 7689 Log.v(LOGTAG, "doTrackball keyCode=" + selectKeyCode 7690 + " count=" + count 7691 + " mTrackballRemainsX=" + mTrackballRemainsX 7692 + " mTrackballRemainsY=" + mTrackballRemainsY); 7693 } 7694 if (mNativeClass != 0 && nativePageShouldHandleShiftAndArrows()) { 7695 for (int i = 0; i < count; i++) { 7696 letPageHandleNavKey(selectKeyCode, time, true, metaState); 7697 } 7698 letPageHandleNavKey(selectKeyCode, time, false, metaState); 7699 } else if (navHandledKey(selectKeyCode, count, false, time)) { 7700 playSoundEffect(keyCodeToSoundsEffect(selectKeyCode)); 7701 } 7702 mTrackballRemainsX = mTrackballRemainsY = 0; 7703 } 7704 if (count >= TRACKBALL_SCROLL_COUNT) { 7705 int xMove = scaleTrackballX(xRate, width); 7706 int yMove = scaleTrackballY(yRate, height); 7707 if (DebugFlags.WEB_VIEW) { 7708 Log.v(LOGTAG, "doTrackball pinScrollBy" 7709 + " count=" + count 7710 + " xMove=" + xMove + " yMove=" + yMove 7711 + " mScrollX-oldScrollX=" + (mScrollX-oldScrollX) 7712 + " mScrollY-oldScrollY=" + (mScrollY-oldScrollY) 7713 ); 7714 } 7715 if (Math.abs(mScrollX - oldScrollX) > Math.abs(xMove)) { 7716 xMove = 0; 7717 } 7718 if (Math.abs(mScrollY - oldScrollY) > Math.abs(yMove)) { 7719 yMove = 0; 7720 } 7721 if (xMove != 0 || yMove != 0) { 7722 pinScrollBy(xMove, yMove, true, 0); 7723 } 7724 } 7725 } 7726 7727 /** 7728 * Compute the maximum horizontal scroll position. Used by {@link OverScrollGlow}. 7729 * @return Maximum horizontal scroll position within real content 7730 */ 7731 int computeMaxScrollX() { 7732 return Math.max(computeRealHorizontalScrollRange() - getViewWidth(), 0); 7733 } 7734 7735 /** 7736 * Compute the maximum vertical scroll position. Used by {@link OverScrollGlow}. 7737 * @return Maximum vertical scroll position within real content 7738 */ 7739 int computeMaxScrollY() { 7740 return Math.max(computeRealVerticalScrollRange() + getTitleHeight() 7741 - getViewHeightWithTitle(), 0); 7742 } 7743 7744 boolean updateScrollCoordinates(int x, int y) { 7745 int oldX = mScrollX; 7746 int oldY = mScrollY; 7747 mScrollX = x; 7748 mScrollY = y; 7749 if (oldX != mScrollX || oldY != mScrollY) { 7750 onScrollChanged(mScrollX, mScrollY, oldX, oldY); 7751 return true; 7752 } else { 7753 return false; 7754 } 7755 } 7756 7757 public void flingScroll(int vx, int vy) { 7758 checkThread(); 7759 mScroller.fling(mScrollX, mScrollY, vx, vy, 0, computeMaxScrollX(), 0, 7760 computeMaxScrollY(), mOverflingDistance, mOverflingDistance); 7761 invalidate(); 7762 } 7763 7764 private void doFling() { 7765 if (mVelocityTracker == null) { 7766 return; 7767 } 7768 int maxX = computeMaxScrollX(); 7769 int maxY = computeMaxScrollY(); 7770 7771 mVelocityTracker.computeCurrentVelocity(1000, mMaximumFling); 7772 int vx = (int) mVelocityTracker.getXVelocity(); 7773 int vy = (int) mVelocityTracker.getYVelocity(); 7774 7775 int scrollX = mScrollX; 7776 int scrollY = mScrollY; 7777 int overscrollDistance = mOverscrollDistance; 7778 int overflingDistance = mOverflingDistance; 7779 7780 // Use the layer's scroll data if applicable. 7781 if (mTouchMode == TOUCH_DRAG_LAYER_MODE) { 7782 scrollX = mScrollingLayerRect.left; 7783 scrollY = mScrollingLayerRect.top; 7784 maxX = mScrollingLayerRect.right; 7785 maxY = mScrollingLayerRect.bottom; 7786 // No overscrolling for layers. 7787 overscrollDistance = overflingDistance = 0; 7788 } 7789 7790 if (mSnapScrollMode != SNAP_NONE) { 7791 if ((mSnapScrollMode & SNAP_X) == SNAP_X) { 7792 vy = 0; 7793 } else { 7794 vx = 0; 7795 } 7796 } 7797 if ((maxX == 0 && vy == 0) || (maxY == 0 && vx == 0)) { 7798 WebViewCore.resumePriority(); 7799 if (!mSelectingText) { 7800 WebViewCore.resumeUpdatePicture(mWebViewCore); 7801 } 7802 if (mScroller.springBack(scrollX, scrollY, 0, maxX, 0, maxY)) { 7803 invalidate(); 7804 } 7805 return; 7806 } 7807 float currentVelocity = mScroller.getCurrVelocity(); 7808 float velocity = (float) Math.hypot(vx, vy); 7809 if (mLastVelocity > 0 && currentVelocity > 0 && velocity 7810 > mLastVelocity * MINIMUM_VELOCITY_RATIO_FOR_ACCELERATION) { 7811 float deltaR = (float) (Math.abs(Math.atan2(mLastVelY, mLastVelX) 7812 - Math.atan2(vy, vx))); 7813 final float circle = (float) (Math.PI) * 2.0f; 7814 if (deltaR > circle * 0.9f || deltaR < circle * 0.1f) { 7815 vx += currentVelocity * mLastVelX / mLastVelocity; 7816 vy += currentVelocity * mLastVelY / mLastVelocity; 7817 velocity = (float) Math.hypot(vx, vy); 7818 if (DebugFlags.WEB_VIEW) { 7819 Log.v(LOGTAG, "doFling vx= " + vx + " vy=" + vy); 7820 } 7821 } else if (DebugFlags.WEB_VIEW) { 7822 Log.v(LOGTAG, "doFling missed " + deltaR / circle); 7823 } 7824 } else if (DebugFlags.WEB_VIEW) { 7825 Log.v(LOGTAG, "doFling start last=" + mLastVelocity 7826 + " current=" + currentVelocity 7827 + " vx=" + vx + " vy=" + vy 7828 + " maxX=" + maxX + " maxY=" + maxY 7829 + " scrollX=" + scrollX + " scrollY=" + scrollY 7830 + " layer=" + mCurrentScrollingLayerId); 7831 } 7832 7833 // Allow sloppy flings without overscrolling at the edges. 7834 if ((scrollX == 0 || scrollX == maxX) && Math.abs(vx) < Math.abs(vy)) { 7835 vx = 0; 7836 } 7837 if ((scrollY == 0 || scrollY == maxY) && Math.abs(vy) < Math.abs(vx)) { 7838 vy = 0; 7839 } 7840 7841 if (overscrollDistance < overflingDistance) { 7842 if ((vx > 0 && scrollX == -overscrollDistance) || 7843 (vx < 0 && scrollX == maxX + overscrollDistance)) { 7844 vx = 0; 7845 } 7846 if ((vy > 0 && scrollY == -overscrollDistance) || 7847 (vy < 0 && scrollY == maxY + overscrollDistance)) { 7848 vy = 0; 7849 } 7850 } 7851 7852 mLastVelX = vx; 7853 mLastVelY = vy; 7854 mLastVelocity = velocity; 7855 7856 // no horizontal overscroll if the content just fits 7857 mScroller.fling(scrollX, scrollY, -vx, -vy, 0, maxX, 0, maxY, 7858 maxX == 0 ? 0 : overflingDistance, overflingDistance); 7859 // Duration is calculated based on velocity. With range boundaries and overscroll 7860 // we may not know how long the final animation will take. (Hence the deprecation 7861 // warning on the call below.) It's not a big deal for scroll bars but if webcore 7862 // resumes during this effect we will take a performance hit. See computeScroll; 7863 // we resume webcore there when the animation is finished. 7864 final int time = mScroller.getDuration(); 7865 7866 // Suppress scrollbars for layer scrolling. 7867 if (mTouchMode != TOUCH_DRAG_LAYER_MODE) { 7868 awakenScrollBars(time); 7869 } 7870 7871 invalidate(); 7872 } 7873 7874 /** 7875 * Returns a view containing zoom controls i.e. +/- buttons. The caller is 7876 * in charge of installing this view to the view hierarchy. This view will 7877 * become visible when the user starts scrolling via touch and fade away if 7878 * the user does not interact with it. 7879 * <p/> 7880 * API version 3 introduces a built-in zoom mechanism that is shown 7881 * automatically by the MapView. This is the preferred approach for 7882 * showing the zoom UI. 7883 * 7884 * @deprecated The built-in zoom mechanism is preferred, see 7885 * {@link WebSettings#setBuiltInZoomControls(boolean)}. 7886 */ 7887 @Deprecated 7888 public View getZoomControls() { 7889 checkThread(); 7890 if (!getSettings().supportZoom()) { 7891 Log.w(LOGTAG, "This WebView doesn't support zoom."); 7892 return null; 7893 } 7894 return mZoomManager.getExternalZoomPicker(); 7895 } 7896 7897 void dismissZoomControl() { 7898 mZoomManager.dismissZoomPicker(); 7899 } 7900 7901 float getDefaultZoomScale() { 7902 return mZoomManager.getDefaultScale(); 7903 } 7904 7905 /** 7906 * Return the overview scale of the WebView 7907 * @return The overview scale. 7908 */ 7909 float getZoomOverviewScale() { 7910 return mZoomManager.getZoomOverviewScale(); 7911 } 7912 7913 /** 7914 * @return TRUE if the WebView can be zoomed in. 7915 */ 7916 public boolean canZoomIn() { 7917 checkThread(); 7918 return mZoomManager.canZoomIn(); 7919 } 7920 7921 /** 7922 * @return TRUE if the WebView can be zoomed out. 7923 */ 7924 public boolean canZoomOut() { 7925 checkThread(); 7926 return mZoomManager.canZoomOut(); 7927 } 7928 7929 /** 7930 * Perform zoom in in the webview 7931 * @return TRUE if zoom in succeeds. FALSE if no zoom changes. 7932 */ 7933 public boolean zoomIn() { 7934 checkThread(); 7935 return mZoomManager.zoomIn(); 7936 } 7937 7938 /** 7939 * Perform zoom out in the webview 7940 * @return TRUE if zoom out succeeds. FALSE if no zoom changes. 7941 */ 7942 public boolean zoomOut() { 7943 checkThread(); 7944 return mZoomManager.zoomOut(); 7945 } 7946 7947 /** 7948 * This selects the best clickable target at mLastTouchX and mLastTouchY 7949 * and calls showCursorTimed on the native side 7950 */ 7951 private void updateSelection() { 7952 if (mNativeClass == 0 || sDisableNavcache) { 7953 return; 7954 } 7955 mPrivateHandler.removeMessages(UPDATE_SELECTION); 7956 // mLastTouchX and mLastTouchY are the point in the current viewport 7957 int contentX = viewToContentX(mLastTouchX + mScrollX); 7958 int contentY = viewToContentY(mLastTouchY + mScrollY); 7959 int slop = viewToContentDimension(mNavSlop); 7960 Rect rect = new Rect(contentX - slop, contentY - slop, 7961 contentX + slop, contentY + slop); 7962 nativeSelectBestAt(rect); 7963 mInitialHitTestResult = hitTestResult(null); 7964 } 7965 7966 /** 7967 * Scroll the focused text field to match the WebTextView 7968 * @param xPercent New x position of the WebTextView from 0 to 1. 7969 */ 7970 /*package*/ void scrollFocusedTextInputX(float xPercent) { 7971 if (!inEditingMode() || mWebViewCore == null) { 7972 return; 7973 } 7974 mWebViewCore.sendMessage(EventHub.SCROLL_TEXT_INPUT, 0, 7975 new Float(xPercent)); 7976 } 7977 7978 /** 7979 * Scroll the focused textarea vertically to match the WebTextView 7980 * @param y New y position of the WebTextView in view coordinates 7981 */ 7982 /* package */ void scrollFocusedTextInputY(int y) { 7983 if (!inEditingMode() || mWebViewCore == null) { 7984 return; 7985 } 7986 mWebViewCore.sendMessage(EventHub.SCROLL_TEXT_INPUT, 0, viewToContentDimension(y)); 7987 } 7988 7989 /** 7990 * Set our starting point and time for a drag from the WebTextView. 7991 */ 7992 /*package*/ void initiateTextFieldDrag(float x, float y, long eventTime) { 7993 if (!inEditingMode()) { 7994 return; 7995 } 7996 mLastTouchX = Math.round(x + mWebTextView.getLeft() - mScrollX); 7997 mLastTouchY = Math.round(y + mWebTextView.getTop() - mScrollY); 7998 mLastTouchTime = eventTime; 7999 if (!mScroller.isFinished()) { 8000 abortAnimation(); 8001 } 8002 mSnapScrollMode = SNAP_NONE; 8003 mVelocityTracker = VelocityTracker.obtain(); 8004 mTouchMode = TOUCH_DRAG_START_MODE; 8005 } 8006 8007 /** 8008 * Given a motion event from the WebTextView, set its location to our 8009 * coordinates, and handle the event. 8010 */ 8011 /*package*/ boolean textFieldDrag(MotionEvent event) { 8012 if (!inEditingMode()) { 8013 return false; 8014 } 8015 mDragFromTextInput = true; 8016 event.offsetLocation((mWebTextView.getLeft() - mScrollX), 8017 (mWebTextView.getTop() - mScrollY)); 8018 boolean result = onTouchEvent(event); 8019 mDragFromTextInput = false; 8020 return result; 8021 } 8022 8023 /** 8024 * Due a touch up from a WebTextView. This will be handled by webkit to 8025 * change the selection. 8026 * @param event MotionEvent in the WebTextView's coordinates. 8027 */ 8028 /*package*/ void touchUpOnTextField(MotionEvent event) { 8029 if (!inEditingMode()) { 8030 return; 8031 } 8032 int x = viewToContentX((int) event.getX() + mWebTextView.getLeft()); 8033 int y = viewToContentY((int) event.getY() + mWebTextView.getTop()); 8034 int slop = viewToContentDimension(mNavSlop); 8035 nativeMotionUp(x, y, slop); 8036 } 8037 8038 /** 8039 * Called when pressing the center key or trackball on a textfield. 8040 */ 8041 /*package*/ void centerKeyPressOnTextField() { 8042 mWebViewCore.sendMessage(EventHub.CLICK, nativeCursorFramePointer(), 8043 nativeCursorNodePointer()); 8044 } 8045 8046 private void doShortPress() { 8047 if (mNativeClass == 0) { 8048 return; 8049 } 8050 if (mPreventDefault == PREVENT_DEFAULT_YES) { 8051 return; 8052 } 8053 mTouchMode = TOUCH_DONE_MODE; 8054 updateSelection(); 8055 switchOutDrawHistory(); 8056 // mLastTouchX and mLastTouchY are the point in the current viewport 8057 int contentX = viewToContentX(mLastTouchX + mScrollX); 8058 int contentY = viewToContentY(mLastTouchY + mScrollY); 8059 int slop = viewToContentDimension(mNavSlop); 8060 if (sDisableNavcache && !mTouchHighlightRegion.isEmpty()) { 8061 // set mTouchHighlightRequested to 0 to cause an immediate 8062 // drawing of the touch rings 8063 mTouchHighlightRequested = 0; 8064 invalidate(mTouchHighlightRegion.getBounds()); 8065 mPrivateHandler.postDelayed(new Runnable() { 8066 @Override 8067 public void run() { 8068 removeTouchHighlight(); 8069 } 8070 }, ViewConfiguration.getPressedStateDuration()); 8071 } 8072 if (mFocusedNode != null && mFocusedNode.mIntentUrl != null) { 8073 playSoundEffect(SoundEffectConstants.CLICK); 8074 overrideLoading(mFocusedNode.mIntentUrl); 8075 } else if (sDisableNavcache) { 8076 WebViewCore.TouchUpData touchUpData = new WebViewCore.TouchUpData(); 8077 // use "0" as generation id to inform WebKit to use the same x/y as 8078 // it used when processing GET_TOUCH_HIGHLIGHT_RECTS 8079 touchUpData.mMoveGeneration = 0; 8080 mWebViewCore.sendMessage(EventHub.TOUCH_UP, touchUpData); 8081 } else if (nativePointInNavCache(contentX, contentY, slop)) { 8082 WebViewCore.MotionUpData motionUpData = new WebViewCore 8083 .MotionUpData(); 8084 motionUpData.mFrame = nativeCacheHitFramePointer(); 8085 motionUpData.mNode = nativeCacheHitNodePointer(); 8086 motionUpData.mBounds = nativeCacheHitNodeBounds(); 8087 motionUpData.mX = contentX; 8088 motionUpData.mY = contentY; 8089 mWebViewCore.sendMessageAtFrontOfQueue(EventHub.VALID_NODE_BOUNDS, 8090 motionUpData); 8091 } else { 8092 doMotionUp(contentX, contentY); 8093 } 8094 } 8095 8096 private void doMotionUp(int contentX, int contentY) { 8097 int slop = viewToContentDimension(mNavSlop); 8098 if (nativeMotionUp(contentX, contentY, slop) && mLogEvent) { 8099 EventLog.writeEvent(EventLogTags.BROWSER_SNAP_CENTER); 8100 } 8101 if (nativeHasCursorNode() && !nativeCursorIsTextInput()) { 8102 playSoundEffect(SoundEffectConstants.CLICK); 8103 } 8104 } 8105 8106 void sendPluginDrawMsg() { 8107 mWebViewCore.sendMessage(EventHub.PLUGIN_SURFACE_READY); 8108 } 8109 8110 /** 8111 * Returns plugin bounds if x/y in content coordinates corresponds to a 8112 * plugin. Otherwise a NULL rectangle is returned. 8113 */ 8114 Rect getPluginBounds(int x, int y) { 8115 int slop = viewToContentDimension(mNavSlop); 8116 if (nativePointInNavCache(x, y, slop) && nativeCacheHitIsPlugin()) { 8117 return nativeCacheHitNodeBounds(); 8118 } else { 8119 return null; 8120 } 8121 } 8122 8123 /* 8124 * Return true if the rect (e.g. plugin) is fully visible and maximized 8125 * inside the WebView. 8126 */ 8127 boolean isRectFitOnScreen(Rect rect) { 8128 final int rectWidth = rect.width(); 8129 final int rectHeight = rect.height(); 8130 final int viewWidth = getViewWidth(); 8131 final int viewHeight = getViewHeightWithTitle(); 8132 float scale = Math.min((float) viewWidth / rectWidth, (float) viewHeight / rectHeight); 8133 scale = mZoomManager.computeScaleWithLimits(scale); 8134 return !mZoomManager.willScaleTriggerZoom(scale) 8135 && contentToViewX(rect.left) >= mScrollX 8136 && contentToViewX(rect.right) <= mScrollX + viewWidth 8137 && contentToViewY(rect.top) >= mScrollY 8138 && contentToViewY(rect.bottom) <= mScrollY + viewHeight; 8139 } 8140 8141 /* 8142 * Maximize and center the rectangle, specified in the document coordinate 8143 * space, inside the WebView. If the zoom doesn't need to be changed, do an 8144 * animated scroll to center it. If the zoom needs to be changed, find the 8145 * zoom center and do a smooth zoom transition. The rect is in document 8146 * coordinates 8147 */ 8148 void centerFitRect(Rect rect) { 8149 final int rectWidth = rect.width(); 8150 final int rectHeight = rect.height(); 8151 final int viewWidth = getViewWidth(); 8152 final int viewHeight = getViewHeightWithTitle(); 8153 float scale = Math.min((float) viewWidth / rectWidth, (float) viewHeight 8154 / rectHeight); 8155 scale = mZoomManager.computeScaleWithLimits(scale); 8156 if (!mZoomManager.willScaleTriggerZoom(scale)) { 8157 pinScrollTo(contentToViewX(rect.left + rectWidth / 2) - viewWidth / 2, 8158 contentToViewY(rect.top + rectHeight / 2) - viewHeight / 2, 8159 true, 0); 8160 } else { 8161 float actualScale = mZoomManager.getScale(); 8162 float oldScreenX = rect.left * actualScale - mScrollX; 8163 float rectViewX = rect.left * scale; 8164 float rectViewWidth = rectWidth * scale; 8165 float newMaxWidth = mContentWidth * scale; 8166 float newScreenX = (viewWidth - rectViewWidth) / 2; 8167 // pin the newX to the WebView 8168 if (newScreenX > rectViewX) { 8169 newScreenX = rectViewX; 8170 } else if (newScreenX > (newMaxWidth - rectViewX - rectViewWidth)) { 8171 newScreenX = viewWidth - (newMaxWidth - rectViewX); 8172 } 8173 float zoomCenterX = (oldScreenX * scale - newScreenX * actualScale) 8174 / (scale - actualScale); 8175 float oldScreenY = rect.top * actualScale + getTitleHeight() 8176 - mScrollY; 8177 float rectViewY = rect.top * scale + getTitleHeight(); 8178 float rectViewHeight = rectHeight * scale; 8179 float newMaxHeight = mContentHeight * scale + getTitleHeight(); 8180 float newScreenY = (viewHeight - rectViewHeight) / 2; 8181 // pin the newY to the WebView 8182 if (newScreenY > rectViewY) { 8183 newScreenY = rectViewY; 8184 } else if (newScreenY > (newMaxHeight - rectViewY - rectViewHeight)) { 8185 newScreenY = viewHeight - (newMaxHeight - rectViewY); 8186 } 8187 float zoomCenterY = (oldScreenY * scale - newScreenY * actualScale) 8188 / (scale - actualScale); 8189 mZoomManager.setZoomCenter(zoomCenterX, zoomCenterY); 8190 mZoomManager.startZoomAnimation(scale, false); 8191 } 8192 } 8193 8194 // Called by JNI to handle a touch on a node representing an email address, 8195 // address, or phone number 8196 private void overrideLoading(String url) { 8197 mCallbackProxy.uiOverrideUrlLoading(url); 8198 } 8199 8200 @Override 8201 public boolean requestFocus(int direction, Rect previouslyFocusedRect) { 8202 // FIXME: If a subwindow is showing find, and the user touches the 8203 // background window, it can steal focus. 8204 if (mFindIsUp) return false; 8205 boolean result = false; 8206 if (inEditingMode()) { 8207 result = mWebTextView.requestFocus(direction, 8208 previouslyFocusedRect); 8209 } else { 8210 result = super.requestFocus(direction, previouslyFocusedRect); 8211 if (mWebViewCore.getSettings().getNeedInitialFocus() && !isInTouchMode()) { 8212 // For cases such as GMail, where we gain focus from a direction, 8213 // we want to move to the first available link. 8214 // FIXME: If there are no visible links, we may not want to 8215 int fakeKeyDirection = 0; 8216 switch(direction) { 8217 case View.FOCUS_UP: 8218 fakeKeyDirection = KeyEvent.KEYCODE_DPAD_UP; 8219 break; 8220 case View.FOCUS_DOWN: 8221 fakeKeyDirection = KeyEvent.KEYCODE_DPAD_DOWN; 8222 break; 8223 case View.FOCUS_LEFT: 8224 fakeKeyDirection = KeyEvent.KEYCODE_DPAD_LEFT; 8225 break; 8226 case View.FOCUS_RIGHT: 8227 fakeKeyDirection = KeyEvent.KEYCODE_DPAD_RIGHT; 8228 break; 8229 default: 8230 return result; 8231 } 8232 if (mNativeClass != 0 && !nativeHasCursorNode()) { 8233 navHandledKey(fakeKeyDirection, 1, true, 0); 8234 } 8235 } 8236 } 8237 return result; 8238 } 8239 8240 @Override 8241 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 8242 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 8243 8244 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 8245 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 8246 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 8247 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 8248 8249 int measuredHeight = heightSize; 8250 int measuredWidth = widthSize; 8251 8252 // Grab the content size from WebViewCore. 8253 int contentHeight = contentToViewDimension(mContentHeight); 8254 int contentWidth = contentToViewDimension(mContentWidth); 8255 8256// Log.d(LOGTAG, "------- measure " + heightMode); 8257 8258 if (heightMode != MeasureSpec.EXACTLY) { 8259 mHeightCanMeasure = true; 8260 measuredHeight = contentHeight; 8261 if (heightMode == MeasureSpec.AT_MOST) { 8262 // If we are larger than the AT_MOST height, then our height can 8263 // no longer be measured and we should scroll internally. 8264 if (measuredHeight > heightSize) { 8265 measuredHeight = heightSize; 8266 mHeightCanMeasure = false; 8267 measuredHeight |= MEASURED_STATE_TOO_SMALL; 8268 } 8269 } 8270 } else { 8271 mHeightCanMeasure = false; 8272 } 8273 if (mNativeClass != 0) { 8274 nativeSetHeightCanMeasure(mHeightCanMeasure); 8275 } 8276 // For the width, always use the given size unless unspecified. 8277 if (widthMode == MeasureSpec.UNSPECIFIED) { 8278 mWidthCanMeasure = true; 8279 measuredWidth = contentWidth; 8280 } else { 8281 if (measuredWidth < contentWidth) { 8282 measuredWidth |= MEASURED_STATE_TOO_SMALL; 8283 } 8284 mWidthCanMeasure = false; 8285 } 8286 8287 synchronized (this) { 8288 setMeasuredDimension(measuredWidth, measuredHeight); 8289 } 8290 } 8291 8292 @Override 8293 public boolean requestChildRectangleOnScreen(View child, 8294 Rect rect, 8295 boolean immediate) { 8296 if (mNativeClass == 0) { 8297 return false; 8298 } 8299 // don't scroll while in zoom animation. When it is done, we will adjust 8300 // the necessary components (e.g., WebTextView if it is in editing mode) 8301 if (mZoomManager.isFixedLengthAnimationInProgress()) { 8302 return false; 8303 } 8304 8305 rect.offset(child.getLeft() - child.getScrollX(), 8306 child.getTop() - child.getScrollY()); 8307 8308 Rect content = new Rect(viewToContentX(mScrollX), 8309 viewToContentY(mScrollY), 8310 viewToContentX(mScrollX + getWidth() 8311 - getVerticalScrollbarWidth()), 8312 viewToContentY(mScrollY + getViewHeightWithTitle())); 8313 content = nativeSubtractLayers(content); 8314 int screenTop = contentToViewY(content.top); 8315 int screenBottom = contentToViewY(content.bottom); 8316 int height = screenBottom - screenTop; 8317 int scrollYDelta = 0; 8318 8319 if (rect.bottom > screenBottom) { 8320 int oneThirdOfScreenHeight = height / 3; 8321 if (rect.height() > 2 * oneThirdOfScreenHeight) { 8322 // If the rectangle is too tall to fit in the bottom two thirds 8323 // of the screen, place it at the top. 8324 scrollYDelta = rect.top - screenTop; 8325 } else { 8326 // If the rectangle will still fit on screen, we want its 8327 // top to be in the top third of the screen. 8328 scrollYDelta = rect.top - (screenTop + oneThirdOfScreenHeight); 8329 } 8330 } else if (rect.top < screenTop) { 8331 scrollYDelta = rect.top - screenTop; 8332 } 8333 8334 int screenLeft = contentToViewX(content.left); 8335 int screenRight = contentToViewX(content.right); 8336 int width = screenRight - screenLeft; 8337 int scrollXDelta = 0; 8338 8339 if (rect.right > screenRight && rect.left > screenLeft) { 8340 if (rect.width() > width) { 8341 scrollXDelta += (rect.left - screenLeft); 8342 } else { 8343 scrollXDelta += (rect.right - screenRight); 8344 } 8345 } else if (rect.left < screenLeft) { 8346 scrollXDelta -= (screenLeft - rect.left); 8347 } 8348 8349 if ((scrollYDelta | scrollXDelta) != 0) { 8350 return pinScrollBy(scrollXDelta, scrollYDelta, !immediate, 0); 8351 } 8352 8353 return false; 8354 } 8355 8356 /* package */ void replaceTextfieldText(int oldStart, int oldEnd, 8357 String replace, int newStart, int newEnd) { 8358 WebViewCore.ReplaceTextData arg = new WebViewCore.ReplaceTextData(); 8359 arg.mReplace = replace; 8360 arg.mNewStart = newStart; 8361 arg.mNewEnd = newEnd; 8362 mTextGeneration++; 8363 arg.mTextGeneration = mTextGeneration; 8364 mWebViewCore.sendMessage(EventHub.REPLACE_TEXT, oldStart, oldEnd, arg); 8365 } 8366 8367 /* package */ void passToJavaScript(String currentText, KeyEvent event) { 8368 // check if mWebViewCore has been destroyed 8369 if (mWebViewCore == null) { 8370 return; 8371 } 8372 WebViewCore.JSKeyData arg = new WebViewCore.JSKeyData(); 8373 arg.mEvent = event; 8374 arg.mCurrentText = currentText; 8375 // Increase our text generation number, and pass it to webcore thread 8376 mTextGeneration++; 8377 mWebViewCore.sendMessage(EventHub.PASS_TO_JS, mTextGeneration, 0, arg); 8378 // WebKit's document state is not saved until about to leave the page. 8379 // To make sure the host application, like Browser, has the up to date 8380 // document state when it goes to background, we force to save the 8381 // document state. 8382 mWebViewCore.removeMessages(EventHub.SAVE_DOCUMENT_STATE); 8383 mWebViewCore.sendMessageDelayed(EventHub.SAVE_DOCUMENT_STATE, 8384 cursorData(), 1000); 8385 } 8386 8387 /** 8388 * @hide 8389 */ 8390 public synchronized WebViewCore getWebViewCore() { 8391 return mWebViewCore; 8392 } 8393 8394 /** 8395 * Used only by TouchEventQueue to store pending touch events. 8396 */ 8397 private static class QueuedTouch { 8398 long mSequence; 8399 MotionEvent mEvent; // Optional 8400 TouchEventData mTed; // Optional 8401 8402 QueuedTouch mNext; 8403 8404 public QueuedTouch set(TouchEventData ted) { 8405 mSequence = ted.mSequence; 8406 mTed = ted; 8407 mEvent = null; 8408 mNext = null; 8409 return this; 8410 } 8411 8412 public QueuedTouch set(MotionEvent ev, long sequence) { 8413 mEvent = MotionEvent.obtain(ev); 8414 mSequence = sequence; 8415 mTed = null; 8416 mNext = null; 8417 return this; 8418 } 8419 8420 public QueuedTouch add(QueuedTouch other) { 8421 if (other.mSequence < mSequence) { 8422 other.mNext = this; 8423 return other; 8424 } 8425 8426 QueuedTouch insertAt = this; 8427 while (insertAt.mNext != null && insertAt.mNext.mSequence < other.mSequence) { 8428 insertAt = insertAt.mNext; 8429 } 8430 other.mNext = insertAt.mNext; 8431 insertAt.mNext = other; 8432 return this; 8433 } 8434 } 8435 8436 /** 8437 * WebView handles touch events asynchronously since some events must be passed to WebKit 8438 * for potentially slower processing. TouchEventQueue serializes touch events regardless 8439 * of which path they take to ensure that no events are ever processed out of order 8440 * by WebView. 8441 */ 8442 private class TouchEventQueue { 8443 private long mNextTouchSequence = Long.MIN_VALUE + 1; 8444 private long mLastHandledTouchSequence = Long.MIN_VALUE; 8445 private long mIgnoreUntilSequence = Long.MIN_VALUE + 1; 8446 8447 // Events waiting to be processed. 8448 private QueuedTouch mTouchEventQueue; 8449 8450 // Known events that are waiting on a response before being enqueued. 8451 private QueuedTouch mPreQueue; 8452 8453 // Pool of QueuedTouch objects saved for later use. 8454 private QueuedTouch mQueuedTouchRecycleBin; 8455 private int mQueuedTouchRecycleCount; 8456 8457 private long mLastEventTime = Long.MAX_VALUE; 8458 private static final int MAX_RECYCLED_QUEUED_TOUCH = 15; 8459 8460 // milliseconds until we abandon hope of getting all of a previous gesture 8461 private static final int QUEUED_GESTURE_TIMEOUT = 1000; 8462 8463 private QueuedTouch obtainQueuedTouch() { 8464 if (mQueuedTouchRecycleBin != null) { 8465 QueuedTouch result = mQueuedTouchRecycleBin; 8466 mQueuedTouchRecycleBin = result.mNext; 8467 mQueuedTouchRecycleCount--; 8468 return result; 8469 } 8470 return new QueuedTouch(); 8471 } 8472 8473 /** 8474 * Allow events with any currently missing sequence numbers to be skipped in processing. 8475 */ 8476 public void ignoreCurrentlyMissingEvents() { 8477 mIgnoreUntilSequence = mNextTouchSequence; 8478 8479 // Run any events we have available and complete, pre-queued or otherwise. 8480 runQueuedAndPreQueuedEvents(); 8481 } 8482 8483 private void runQueuedAndPreQueuedEvents() { 8484 QueuedTouch qd = mPreQueue; 8485 boolean fromPreQueue = true; 8486 while (qd != null && qd.mSequence == mLastHandledTouchSequence + 1) { 8487 handleQueuedTouch(qd); 8488 QueuedTouch recycleMe = qd; 8489 if (fromPreQueue) { 8490 mPreQueue = qd.mNext; 8491 } else { 8492 mTouchEventQueue = qd.mNext; 8493 } 8494 recycleQueuedTouch(recycleMe); 8495 mLastHandledTouchSequence++; 8496 8497 long nextPre = mPreQueue != null ? mPreQueue.mSequence : Long.MAX_VALUE; 8498 long nextQueued = mTouchEventQueue != null ? 8499 mTouchEventQueue.mSequence : Long.MAX_VALUE; 8500 fromPreQueue = nextPre < nextQueued; 8501 qd = fromPreQueue ? mPreQueue : mTouchEventQueue; 8502 } 8503 } 8504 8505 /** 8506 * Add a TouchEventData to the pre-queue. 8507 * 8508 * An event in the pre-queue is an event that we know about that 8509 * has been sent to webkit, but that we haven't received back and 8510 * enqueued into the normal touch queue yet. If webkit ever times 8511 * out and we need to ignore currently missing events, we'll run 8512 * events from the pre-queue to patch the holes. 8513 * 8514 * @param ted TouchEventData to pre-queue 8515 */ 8516 public void preQueueTouchEventData(TouchEventData ted) { 8517 QueuedTouch newTouch = obtainQueuedTouch().set(ted); 8518 if (mPreQueue == null) { 8519 mPreQueue = newTouch; 8520 } else { 8521 QueuedTouch insertionPoint = mPreQueue; 8522 while (insertionPoint.mNext != null && 8523 insertionPoint.mNext.mSequence < newTouch.mSequence) { 8524 insertionPoint = insertionPoint.mNext; 8525 } 8526 newTouch.mNext = insertionPoint.mNext; 8527 insertionPoint.mNext = newTouch; 8528 } 8529 } 8530 8531 private void recycleQueuedTouch(QueuedTouch qd) { 8532 if (mQueuedTouchRecycleCount < MAX_RECYCLED_QUEUED_TOUCH) { 8533 qd.mNext = mQueuedTouchRecycleBin; 8534 mQueuedTouchRecycleBin = qd; 8535 mQueuedTouchRecycleCount++; 8536 } 8537 } 8538 8539 /** 8540 * Reset the touch event queue. This will dump any pending events 8541 * and reset the sequence numbering. 8542 */ 8543 public void reset() { 8544 mNextTouchSequence = Long.MIN_VALUE + 1; 8545 mLastHandledTouchSequence = Long.MIN_VALUE; 8546 mIgnoreUntilSequence = Long.MIN_VALUE + 1; 8547 while (mTouchEventQueue != null) { 8548 QueuedTouch recycleMe = mTouchEventQueue; 8549 mTouchEventQueue = mTouchEventQueue.mNext; 8550 recycleQueuedTouch(recycleMe); 8551 } 8552 while (mPreQueue != null) { 8553 QueuedTouch recycleMe = mPreQueue; 8554 mPreQueue = mPreQueue.mNext; 8555 recycleQueuedTouch(recycleMe); 8556 } 8557 } 8558 8559 /** 8560 * Return the next valid sequence number for tagging incoming touch events. 8561 * @return The next touch event sequence number 8562 */ 8563 public long nextTouchSequence() { 8564 return mNextTouchSequence++; 8565 } 8566 8567 /** 8568 * Enqueue a touch event in the form of TouchEventData. 8569 * The sequence number will be read from the mSequence field of the argument. 8570 * 8571 * If the touch event's sequence number is the next in line to be processed, it will 8572 * be handled before this method returns. Any subsequent events that have already 8573 * been queued will also be processed in their proper order. 8574 * 8575 * @param ted Touch data to be processed in order. 8576 * @return true if the event was processed before returning, false if it was just enqueued. 8577 */ 8578 public boolean enqueueTouchEvent(TouchEventData ted) { 8579 // Remove from the pre-queue if present 8580 QueuedTouch preQueue = mPreQueue; 8581 if (preQueue != null) { 8582 // On exiting this block, preQueue is set to the pre-queued QueuedTouch object 8583 // if it was present in the pre-queue, and removed from the pre-queue itself. 8584 if (preQueue.mSequence == ted.mSequence) { 8585 mPreQueue = preQueue.mNext; 8586 } else { 8587 QueuedTouch prev = preQueue; 8588 preQueue = null; 8589 while (prev.mNext != null) { 8590 if (prev.mNext.mSequence == ted.mSequence) { 8591 preQueue = prev.mNext; 8592 prev.mNext = preQueue.mNext; 8593 break; 8594 } else { 8595 prev = prev.mNext; 8596 } 8597 } 8598 } 8599 } 8600 8601 if (ted.mSequence < mLastHandledTouchSequence) { 8602 // Stale event and we already moved on; drop it. (Should not be common.) 8603 Log.w(LOGTAG, "Stale touch event " + MotionEvent.actionToString(ted.mAction) + 8604 " received from webcore; ignoring"); 8605 return false; 8606 } 8607 8608 if (dropStaleGestures(ted.mMotionEvent, ted.mSequence)) { 8609 return false; 8610 } 8611 8612 // dropStaleGestures above might have fast-forwarded us to 8613 // an event we have already. 8614 runNextQueuedEvents(); 8615 8616 if (mLastHandledTouchSequence + 1 == ted.mSequence) { 8617 if (preQueue != null) { 8618 recycleQueuedTouch(preQueue); 8619 preQueue = null; 8620 } 8621 handleQueuedTouchEventData(ted); 8622 8623 mLastHandledTouchSequence++; 8624 8625 // Do we have any more? Run them if so. 8626 runNextQueuedEvents(); 8627 } else { 8628 // Reuse the pre-queued object if we had it. 8629 QueuedTouch qd = preQueue != null ? preQueue : obtainQueuedTouch().set(ted); 8630 mTouchEventQueue = mTouchEventQueue == null ? qd : mTouchEventQueue.add(qd); 8631 } 8632 return true; 8633 } 8634 8635 /** 8636 * Enqueue a touch event in the form of a MotionEvent from the framework. 8637 * 8638 * If the touch event's sequence number is the next in line to be processed, it will 8639 * be handled before this method returns. Any subsequent events that have already 8640 * been queued will also be processed in their proper order. 8641 * 8642 * @param ev MotionEvent to be processed in order 8643 */ 8644 public void enqueueTouchEvent(MotionEvent ev) { 8645 final long sequence = nextTouchSequence(); 8646 8647 if (dropStaleGestures(ev, sequence)) { 8648 return; 8649 } 8650 8651 // dropStaleGestures above might have fast-forwarded us to 8652 // an event we have already. 8653 runNextQueuedEvents(); 8654 8655 if (mLastHandledTouchSequence + 1 == sequence) { 8656 handleQueuedMotionEvent(ev); 8657 8658 mLastHandledTouchSequence++; 8659 8660 // Do we have any more? Run them if so. 8661 runNextQueuedEvents(); 8662 } else { 8663 QueuedTouch qd = obtainQueuedTouch().set(ev, sequence); 8664 mTouchEventQueue = mTouchEventQueue == null ? qd : mTouchEventQueue.add(qd); 8665 } 8666 } 8667 8668 private void runNextQueuedEvents() { 8669 QueuedTouch qd = mTouchEventQueue; 8670 while (qd != null && qd.mSequence == mLastHandledTouchSequence + 1) { 8671 handleQueuedTouch(qd); 8672 QueuedTouch recycleMe = qd; 8673 qd = qd.mNext; 8674 recycleQueuedTouch(recycleMe); 8675 mLastHandledTouchSequence++; 8676 } 8677 mTouchEventQueue = qd; 8678 } 8679 8680 private boolean dropStaleGestures(MotionEvent ev, long sequence) { 8681 if (ev != null && ev.getAction() == MotionEvent.ACTION_MOVE && !mConfirmMove) { 8682 // This is to make sure that we don't attempt to process a tap 8683 // or long press when webkit takes too long to get back to us. 8684 // The movement will be properly confirmed when we process the 8685 // enqueued event later. 8686 final int dx = Math.round(ev.getX()) - mLastTouchX; 8687 final int dy = Math.round(ev.getY()) - mLastTouchY; 8688 if (dx * dx + dy * dy > mTouchSlopSquare) { 8689 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); 8690 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); 8691 } 8692 } 8693 8694 if (mTouchEventQueue == null) { 8695 return sequence <= mLastHandledTouchSequence; 8696 } 8697 8698 // If we have a new down event and it's been a while since the last event 8699 // we saw, catch up as best we can and keep going. 8700 if (ev != null && ev.getAction() == MotionEvent.ACTION_DOWN) { 8701 long eventTime = ev.getEventTime(); 8702 long lastHandledEventTime = mLastEventTime; 8703 if (eventTime > lastHandledEventTime + QUEUED_GESTURE_TIMEOUT) { 8704 Log.w(LOGTAG, "Got ACTION_DOWN but still waiting on stale event. " + 8705 "Catching up."); 8706 runQueuedAndPreQueuedEvents(); 8707 8708 // Drop leftovers that we truly don't have. 8709 QueuedTouch qd = mTouchEventQueue; 8710 while (qd != null && qd.mSequence < sequence) { 8711 QueuedTouch recycleMe = qd; 8712 qd = qd.mNext; 8713 recycleQueuedTouch(recycleMe); 8714 } 8715 mTouchEventQueue = qd; 8716 mLastHandledTouchSequence = sequence - 1; 8717 } 8718 } 8719 8720 if (mIgnoreUntilSequence - 1 > mLastHandledTouchSequence) { 8721 QueuedTouch qd = mTouchEventQueue; 8722 while (qd != null && qd.mSequence < mIgnoreUntilSequence) { 8723 QueuedTouch recycleMe = qd; 8724 qd = qd.mNext; 8725 recycleQueuedTouch(recycleMe); 8726 } 8727 mTouchEventQueue = qd; 8728 mLastHandledTouchSequence = mIgnoreUntilSequence - 1; 8729 } 8730 8731 if (mPreQueue != null) { 8732 // Drop stale prequeued events 8733 QueuedTouch qd = mPreQueue; 8734 while (qd != null && qd.mSequence < mIgnoreUntilSequence) { 8735 QueuedTouch recycleMe = qd; 8736 qd = qd.mNext; 8737 recycleQueuedTouch(recycleMe); 8738 } 8739 mPreQueue = qd; 8740 } 8741 8742 return sequence <= mLastHandledTouchSequence; 8743 } 8744 8745 private void handleQueuedTouch(QueuedTouch qt) { 8746 if (qt.mTed != null) { 8747 handleQueuedTouchEventData(qt.mTed); 8748 } else { 8749 handleQueuedMotionEvent(qt.mEvent); 8750 qt.mEvent.recycle(); 8751 } 8752 } 8753 8754 private void handleQueuedMotionEvent(MotionEvent ev) { 8755 mLastEventTime = ev.getEventTime(); 8756 int action = ev.getActionMasked(); 8757 if (ev.getPointerCount() > 1) { // Multi-touch 8758 handleMultiTouchInWebView(ev); 8759 } else { 8760 final ScaleGestureDetector detector = mZoomManager.getMultiTouchGestureDetector(); 8761 if (detector != null && mPreventDefault != PREVENT_DEFAULT_YES) { 8762 // ScaleGestureDetector needs a consistent event stream to operate properly. 8763 // It won't take any action with fewer than two pointers, but it needs to 8764 // update internal bookkeeping state. 8765 detector.onTouchEvent(ev); 8766 } 8767 8768 handleTouchEventCommon(ev, action, Math.round(ev.getX()), Math.round(ev.getY())); 8769 } 8770 } 8771 8772 private void handleQueuedTouchEventData(TouchEventData ted) { 8773 if (ted.mMotionEvent != null) { 8774 mLastEventTime = ted.mMotionEvent.getEventTime(); 8775 } 8776 if (!ted.mReprocess) { 8777 if (ted.mAction == MotionEvent.ACTION_DOWN 8778 && mPreventDefault == PREVENT_DEFAULT_MAYBE_YES) { 8779 // if prevent default is called from WebCore, UI 8780 // will not handle the rest of the touch events any 8781 // more. 8782 mPreventDefault = ted.mNativeResult ? PREVENT_DEFAULT_YES 8783 : PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN; 8784 } else if (ted.mAction == MotionEvent.ACTION_MOVE 8785 && mPreventDefault == PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN) { 8786 // the return for the first ACTION_MOVE will decide 8787 // whether UI will handle touch or not. Currently no 8788 // support for alternating prevent default 8789 mPreventDefault = ted.mNativeResult ? PREVENT_DEFAULT_YES 8790 : PREVENT_DEFAULT_NO; 8791 } 8792 if (mPreventDefault == PREVENT_DEFAULT_YES) { 8793 mTouchHighlightRegion.setEmpty(); 8794 } 8795 } else { 8796 if (ted.mPoints.length > 1) { // multi-touch 8797 if (!ted.mNativeResult && mPreventDefault != PREVENT_DEFAULT_YES) { 8798 mPreventDefault = PREVENT_DEFAULT_NO; 8799 handleMultiTouchInWebView(ted.mMotionEvent); 8800 } else { 8801 mPreventDefault = PREVENT_DEFAULT_YES; 8802 } 8803 return; 8804 } 8805 8806 // prevent default is not called in WebCore, so the 8807 // message needs to be reprocessed in UI 8808 if (!ted.mNativeResult) { 8809 // Following is for single touch. 8810 switch (ted.mAction) { 8811 case MotionEvent.ACTION_DOWN: 8812 mLastDeferTouchX = ted.mPointsInView[0].x; 8813 mLastDeferTouchY = ted.mPointsInView[0].y; 8814 mDeferTouchMode = TOUCH_INIT_MODE; 8815 break; 8816 case MotionEvent.ACTION_MOVE: { 8817 // no snapping in defer process 8818 int x = ted.mPointsInView[0].x; 8819 int y = ted.mPointsInView[0].y; 8820 8821 if (mDeferTouchMode != TOUCH_DRAG_MODE) { 8822 mDeferTouchMode = TOUCH_DRAG_MODE; 8823 mLastDeferTouchX = x; 8824 mLastDeferTouchY = y; 8825 startScrollingLayer(x, y); 8826 startDrag(); 8827 } 8828 int deltaX = pinLocX((int) (mScrollX 8829 + mLastDeferTouchX - x)) 8830 - mScrollX; 8831 int deltaY = pinLocY((int) (mScrollY 8832 + mLastDeferTouchY - y)) 8833 - mScrollY; 8834 doDrag(deltaX, deltaY); 8835 if (deltaX != 0) mLastDeferTouchX = x; 8836 if (deltaY != 0) mLastDeferTouchY = y; 8837 break; 8838 } 8839 case MotionEvent.ACTION_UP: 8840 case MotionEvent.ACTION_CANCEL: 8841 if (mDeferTouchMode == TOUCH_DRAG_MODE) { 8842 // no fling in defer process 8843 mScroller.springBack(mScrollX, mScrollY, 0, 8844 computeMaxScrollX(), 0, 8845 computeMaxScrollY()); 8846 invalidate(); 8847 WebViewCore.resumePriority(); 8848 WebViewCore.resumeUpdatePicture(mWebViewCore); 8849 } 8850 mDeferTouchMode = TOUCH_DONE_MODE; 8851 break; 8852 case WebViewCore.ACTION_DOUBLETAP: 8853 // doDoubleTap() needs mLastTouchX/Y as anchor 8854 mLastDeferTouchX = ted.mPointsInView[0].x; 8855 mLastDeferTouchY = ted.mPointsInView[0].y; 8856 mZoomManager.handleDoubleTap(mLastTouchX, mLastTouchY); 8857 mDeferTouchMode = TOUCH_DONE_MODE; 8858 break; 8859 case WebViewCore.ACTION_LONGPRESS: 8860 HitTestResult hitTest = getHitTestResult(); 8861 if (hitTest != null && hitTest.mType 8862 != HitTestResult.UNKNOWN_TYPE) { 8863 performLongClick(); 8864 } 8865 mDeferTouchMode = TOUCH_DONE_MODE; 8866 break; 8867 } 8868 } 8869 } 8870 } 8871 } 8872 8873 //------------------------------------------------------------------------- 8874 // Methods can be called from a separate thread, like WebViewCore 8875 // If it needs to call the View system, it has to send message. 8876 //------------------------------------------------------------------------- 8877 8878 /** 8879 * General handler to receive message coming from webkit thread 8880 */ 8881 class PrivateHandler extends Handler { 8882 @Override 8883 public void handleMessage(Message msg) { 8884 // exclude INVAL_RECT_MSG_ID since it is frequently output 8885 if (DebugFlags.WEB_VIEW && msg.what != INVAL_RECT_MSG_ID) { 8886 if (msg.what >= FIRST_PRIVATE_MSG_ID 8887 && msg.what <= LAST_PRIVATE_MSG_ID) { 8888 Log.v(LOGTAG, HandlerPrivateDebugString[msg.what 8889 - FIRST_PRIVATE_MSG_ID]); 8890 } else if (msg.what >= FIRST_PACKAGE_MSG_ID 8891 && msg.what <= LAST_PACKAGE_MSG_ID) { 8892 Log.v(LOGTAG, HandlerPackageDebugString[msg.what 8893 - FIRST_PACKAGE_MSG_ID]); 8894 } else { 8895 Log.v(LOGTAG, Integer.toString(msg.what)); 8896 } 8897 } 8898 if (mWebViewCore == null) { 8899 // after WebView's destroy() is called, skip handling messages. 8900 return; 8901 } 8902 if (mBlockWebkitViewMessages 8903 && msg.what != WEBCORE_INITIALIZED_MSG_ID) { 8904 // Blocking messages from webkit 8905 return; 8906 } 8907 switch (msg.what) { 8908 case REMEMBER_PASSWORD: { 8909 mDatabase.setUsernamePassword( 8910 msg.getData().getString("host"), 8911 msg.getData().getString("username"), 8912 msg.getData().getString("password")); 8913 ((Message) msg.obj).sendToTarget(); 8914 break; 8915 } 8916 case NEVER_REMEMBER_PASSWORD: { 8917 mDatabase.setUsernamePassword( 8918 msg.getData().getString("host"), null, null); 8919 ((Message) msg.obj).sendToTarget(); 8920 break; 8921 } 8922 case PREVENT_DEFAULT_TIMEOUT: { 8923 // if timeout happens, cancel it so that it won't block UI 8924 // to continue handling touch events 8925 if ((msg.arg1 == MotionEvent.ACTION_DOWN 8926 && mPreventDefault == PREVENT_DEFAULT_MAYBE_YES) 8927 || (msg.arg1 == MotionEvent.ACTION_MOVE 8928 && mPreventDefault == PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN)) { 8929 cancelWebCoreTouchEvent( 8930 viewToContentX(mLastTouchX + mScrollX), 8931 viewToContentY(mLastTouchY + mScrollY), 8932 true); 8933 } 8934 break; 8935 } 8936 case SCROLL_SELECT_TEXT: { 8937 if (mAutoScrollX == 0 && mAutoScrollY == 0) { 8938 mSentAutoScrollMessage = false; 8939 break; 8940 } 8941 if (mCurrentScrollingLayerId == 0) { 8942 pinScrollBy(mAutoScrollX, mAutoScrollY, true, 0); 8943 } else { 8944 scrollLayerTo(mScrollingLayerRect.left + mAutoScrollX, 8945 mScrollingLayerRect.top + mAutoScrollY); 8946 } 8947 sendEmptyMessageDelayed( 8948 SCROLL_SELECT_TEXT, SELECT_SCROLL_INTERVAL); 8949 break; 8950 } 8951 case UPDATE_SELECTION: { 8952 if (mTouchMode == TOUCH_INIT_MODE 8953 || mTouchMode == TOUCH_SHORTPRESS_MODE 8954 || mTouchMode == TOUCH_SHORTPRESS_START_MODE) { 8955 updateSelection(); 8956 } 8957 break; 8958 } 8959 case SWITCH_TO_SHORTPRESS: { 8960 if (mTouchMode == TOUCH_INIT_MODE) { 8961 if (!sDisableNavcache 8962 && mPreventDefault != PREVENT_DEFAULT_YES) { 8963 mTouchMode = TOUCH_SHORTPRESS_START_MODE; 8964 updateSelection(); 8965 } else { 8966 // set to TOUCH_SHORTPRESS_MODE so that it won't 8967 // trigger double tap any more 8968 mTouchMode = TOUCH_SHORTPRESS_MODE; 8969 } 8970 } else if (mTouchMode == TOUCH_DOUBLE_TAP_MODE) { 8971 mTouchMode = TOUCH_DONE_MODE; 8972 } 8973 break; 8974 } 8975 case SWITCH_TO_LONGPRESS: { 8976 if (sDisableNavcache) { 8977 removeTouchHighlight(); 8978 } 8979 if (inFullScreenMode() || mDeferTouchProcess) { 8980 TouchEventData ted = new TouchEventData(); 8981 ted.mAction = WebViewCore.ACTION_LONGPRESS; 8982 ted.mIds = new int[1]; 8983 ted.mIds[0] = 0; 8984 ted.mPoints = new Point[1]; 8985 ted.mPoints[0] = new Point(viewToContentX(mLastTouchX + mScrollX), 8986 viewToContentY(mLastTouchY + mScrollY)); 8987 ted.mPointsInView = new Point[1]; 8988 ted.mPointsInView[0] = new Point(mLastTouchX, mLastTouchY); 8989 // metaState for long press is tricky. Should it be the 8990 // state when the press started or when the press was 8991 // released? Or some intermediary key state? For 8992 // simplicity for now, we don't set it. 8993 ted.mMetaState = 0; 8994 ted.mReprocess = mDeferTouchProcess; 8995 ted.mNativeLayer = nativeScrollableLayer( 8996 ted.mPoints[0].x, ted.mPoints[0].y, 8997 ted.mNativeLayerRect, null); 8998 ted.mSequence = mTouchEventQueue.nextTouchSequence(); 8999 mTouchEventQueue.preQueueTouchEventData(ted); 9000 mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); 9001 } else if (mPreventDefault != PREVENT_DEFAULT_YES) { 9002 mTouchMode = TOUCH_DONE_MODE; 9003 performLongClick(); 9004 } 9005 break; 9006 } 9007 case RELEASE_SINGLE_TAP: { 9008 doShortPress(); 9009 break; 9010 } 9011 case SCROLL_TO_MSG_ID: { 9012 // arg1 = animate, arg2 = onlyIfImeIsShowing 9013 // obj = Point(x, y) 9014 if (msg.arg2 == 1) { 9015 // This scroll is intended to bring the textfield into 9016 // view, but is only necessary if the IME is showing 9017 InputMethodManager imm = InputMethodManager.peekInstance(); 9018 if (imm == null || !imm.isAcceptingText() 9019 || (!imm.isActive(WebView.this) && (!inEditingMode() 9020 || !imm.isActive(mWebTextView)))) { 9021 break; 9022 } 9023 } 9024 final Point p = (Point) msg.obj; 9025 if (msg.arg1 == 1) { 9026 spawnContentScrollTo(p.x, p.y); 9027 } else { 9028 setContentScrollTo(p.x, p.y); 9029 } 9030 break; 9031 } 9032 case UPDATE_ZOOM_RANGE: { 9033 WebViewCore.ViewState viewState = (WebViewCore.ViewState) msg.obj; 9034 // mScrollX contains the new minPrefWidth 9035 mZoomManager.updateZoomRange(viewState, getViewWidth(), viewState.mScrollX); 9036 break; 9037 } 9038 case UPDATE_ZOOM_DENSITY: { 9039 final float density = (Float) msg.obj; 9040 mZoomManager.updateDefaultZoomDensity(density); 9041 break; 9042 } 9043 case REPLACE_BASE_CONTENT: { 9044 nativeReplaceBaseContent(msg.arg1); 9045 break; 9046 } 9047 case NEW_PICTURE_MSG_ID: { 9048 // called for new content 9049 final WebViewCore.DrawData draw = (WebViewCore.DrawData) msg.obj; 9050 setNewPicture(draw, true); 9051 break; 9052 } 9053 case WEBCORE_INITIALIZED_MSG_ID: 9054 // nativeCreate sets mNativeClass to a non-zero value 9055 String drawableDir = BrowserFrame.getRawResFilename( 9056 BrowserFrame.DRAWABLEDIR, mContext); 9057 WindowManager windowManager = 9058 (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 9059 Display display = windowManager.getDefaultDisplay(); 9060 nativeCreate(msg.arg1, drawableDir, 9061 ActivityManager.isHighEndGfx(display)); 9062 if (mDelaySetPicture != null) { 9063 setNewPicture(mDelaySetPicture, true); 9064 mDelaySetPicture = null; 9065 } 9066 if (mIsPaused) { 9067 nativeSetPauseDrawing(mNativeClass, true); 9068 } 9069 break; 9070 case UPDATE_TEXTFIELD_TEXT_MSG_ID: 9071 // Make sure that the textfield is currently focused 9072 // and representing the same node as the pointer. 9073 if (msg.arg2 == mTextGeneration) { 9074 String text = (String) msg.obj; 9075 if (null == text) { 9076 text = ""; 9077 } 9078 if (inEditingMode() && 9079 mWebTextView.isSameTextField(msg.arg1)) { 9080 mWebTextView.setTextAndKeepSelection(text); 9081 } else if (mInputConnection != null && 9082 mFieldPointer == msg.arg1) { 9083 mInputConnection.setTextAndKeepSelection(text); 9084 } 9085 } 9086 break; 9087 case REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID: 9088 displaySoftKeyboard(true); 9089 // fall through to UPDATE_TEXT_SELECTION_MSG_ID 9090 case UPDATE_TEXT_SELECTION_MSG_ID: 9091 updateTextSelectionFromMessage(msg.arg1, msg.arg2, 9092 (WebViewCore.TextSelectionData) msg.obj); 9093 break; 9094 case FORM_DID_BLUR: 9095 if (inEditingMode() 9096 && mWebTextView.isSameTextField(msg.arg1)) { 9097 hideSoftKeyboard(); 9098 } 9099 break; 9100 case RETURN_LABEL: 9101 if (inEditingMode() 9102 && mWebTextView.isSameTextField(msg.arg1)) { 9103 mWebTextView.setHint((String) msg.obj); 9104 InputMethodManager imm 9105 = InputMethodManager.peekInstance(); 9106 // The hint is propagated to the IME in 9107 // onCreateInputConnection. If the IME is already 9108 // active, restart it so that its hint text is updated. 9109 if (imm != null && imm.isActive(mWebTextView)) { 9110 imm.restartInput(mWebTextView); 9111 } 9112 } 9113 break; 9114 case UNHANDLED_NAV_KEY: 9115 navHandledKey(msg.arg1, 1, false, 0); 9116 break; 9117 case UPDATE_TEXT_ENTRY_MSG_ID: 9118 // this is sent after finishing resize in WebViewCore. Make 9119 // sure the text edit box is still on the screen. 9120 if (inEditingMode() && nativeCursorIsTextInput()) { 9121 updateWebTextViewPosition(); 9122 } 9123 break; 9124 case CLEAR_TEXT_ENTRY: 9125 clearTextEntry(); 9126 break; 9127 case INVAL_RECT_MSG_ID: { 9128 Rect r = (Rect)msg.obj; 9129 if (r == null) { 9130 invalidate(); 9131 } else { 9132 // we need to scale r from content into view coords, 9133 // which viewInvalidate() does for us 9134 viewInvalidate(r.left, r.top, r.right, r.bottom); 9135 } 9136 break; 9137 } 9138 case REQUEST_FORM_DATA: 9139 AutoCompleteAdapter adapter = (AutoCompleteAdapter) msg.obj; 9140 if (mWebTextView.isSameTextField(msg.arg1)) { 9141 mWebTextView.setAdapterCustom(adapter); 9142 } 9143 break; 9144 9145 case LONG_PRESS_CENTER: 9146 // as this is shared by keydown and trackballdown, reset all 9147 // the states 9148 mGotCenterDown = false; 9149 mTrackballDown = false; 9150 performLongClick(); 9151 break; 9152 9153 case WEBCORE_NEED_TOUCH_EVENTS: 9154 mForwardTouchEvents = (msg.arg1 != 0); 9155 break; 9156 9157 case PREVENT_TOUCH_ID: 9158 if (inFullScreenMode()) { 9159 break; 9160 } 9161 TouchEventData ted = (TouchEventData) msg.obj; 9162 9163 if (mTouchEventQueue.enqueueTouchEvent(ted)) { 9164 // WebCore is responding to us; remove pending timeout. 9165 // It will be re-posted when needed. 9166 removeMessages(PREVENT_DEFAULT_TIMEOUT); 9167 } 9168 break; 9169 9170 case REQUEST_KEYBOARD: 9171 if (msg.arg1 == 0) { 9172 hideSoftKeyboard(); 9173 } else { 9174 displaySoftKeyboard(false); 9175 } 9176 break; 9177 9178 case DRAG_HELD_MOTIONLESS: 9179 mHeldMotionless = MOTIONLESS_TRUE; 9180 invalidate(); 9181 // fall through to keep scrollbars awake 9182 9183 case AWAKEN_SCROLL_BARS: 9184 if (mTouchMode == TOUCH_DRAG_MODE 9185 && mHeldMotionless == MOTIONLESS_TRUE) { 9186 awakenScrollBars(ViewConfiguration 9187 .getScrollDefaultDelay(), false); 9188 mPrivateHandler.sendMessageDelayed(mPrivateHandler 9189 .obtainMessage(AWAKEN_SCROLL_BARS), 9190 ViewConfiguration.getScrollDefaultDelay()); 9191 } 9192 break; 9193 9194 case DO_MOTION_UP: 9195 doMotionUp(msg.arg1, msg.arg2); 9196 break; 9197 9198 case SCREEN_ON: 9199 setKeepScreenOn(msg.arg1 == 1); 9200 break; 9201 9202 case ENTER_FULLSCREEN_VIDEO: 9203 int layerId = msg.arg1; 9204 9205 String url = (String) msg.obj; 9206 if (mHTML5VideoViewProxy != null) { 9207 mHTML5VideoViewProxy.enterFullScreenVideo(layerId, url); 9208 } 9209 break; 9210 9211 case EXIT_FULLSCREEN_VIDEO: 9212 if (mHTML5VideoViewProxy != null) { 9213 mHTML5VideoViewProxy.exitFullScreenVideo(); 9214 } 9215 break; 9216 9217 case SHOW_FULLSCREEN: { 9218 View view = (View) msg.obj; 9219 int orientation = msg.arg1; 9220 int npp = msg.arg2; 9221 9222 if (inFullScreenMode()) { 9223 Log.w(LOGTAG, "Should not have another full screen."); 9224 dismissFullScreenMode(); 9225 } 9226 mFullScreenHolder = new PluginFullScreenHolder(WebView.this, orientation, npp); 9227 mFullScreenHolder.setContentView(view); 9228 mFullScreenHolder.show(); 9229 invalidate(); 9230 9231 break; 9232 } 9233 case HIDE_FULLSCREEN: 9234 dismissFullScreenMode(); 9235 break; 9236 9237 case DOM_FOCUS_CHANGED: 9238 if (inEditingMode()) { 9239 nativeClearCursor(); 9240 rebuildWebTextView(); 9241 } 9242 break; 9243 9244 case SHOW_RECT_MSG_ID: { 9245 WebViewCore.ShowRectData data = (WebViewCore.ShowRectData) msg.obj; 9246 int x = mScrollX; 9247 int left = contentToViewX(data.mLeft); 9248 int width = contentToViewDimension(data.mWidth); 9249 int maxWidth = contentToViewDimension(data.mContentWidth); 9250 int viewWidth = getViewWidth(); 9251 if (width < viewWidth) { 9252 // center align 9253 x += left + width / 2 - mScrollX - viewWidth / 2; 9254 } else { 9255 x += (int) (left + data.mXPercentInDoc * width 9256 - mScrollX - data.mXPercentInView * viewWidth); 9257 } 9258 if (DebugFlags.WEB_VIEW) { 9259 Log.v(LOGTAG, "showRectMsg=(left=" + left + ",width=" + 9260 width + ",maxWidth=" + maxWidth + 9261 ",viewWidth=" + viewWidth + ",x=" 9262 + x + ",xPercentInDoc=" + data.mXPercentInDoc + 9263 ",xPercentInView=" + data.mXPercentInView+ ")"); 9264 } 9265 // use the passing content width to cap x as the current 9266 // mContentWidth may not be updated yet 9267 x = Math.max(0, 9268 (Math.min(maxWidth, x + viewWidth)) - viewWidth); 9269 int top = contentToViewY(data.mTop); 9270 int height = contentToViewDimension(data.mHeight); 9271 int maxHeight = contentToViewDimension(data.mContentHeight); 9272 int viewHeight = getViewHeight(); 9273 int y = (int) (top + data.mYPercentInDoc * height - 9274 data.mYPercentInView * viewHeight); 9275 if (DebugFlags.WEB_VIEW) { 9276 Log.v(LOGTAG, "showRectMsg=(top=" + top + ",height=" + 9277 height + ",maxHeight=" + maxHeight + 9278 ",viewHeight=" + viewHeight + ",y=" 9279 + y + ",yPercentInDoc=" + data.mYPercentInDoc + 9280 ",yPercentInView=" + data.mYPercentInView+ ")"); 9281 } 9282 // use the passing content height to cap y as the current 9283 // mContentHeight may not be updated yet 9284 y = Math.max(0, 9285 (Math.min(maxHeight, y + viewHeight) - viewHeight)); 9286 // We need to take into account the visible title height 9287 // when scrolling since y is an absolute view position. 9288 y = Math.max(0, y - getVisibleTitleHeightImpl()); 9289 scrollTo(x, y); 9290 } 9291 break; 9292 9293 case CENTER_FIT_RECT: 9294 centerFitRect((Rect)msg.obj); 9295 break; 9296 9297 case SET_SCROLLBAR_MODES: 9298 mHorizontalScrollBarMode = msg.arg1; 9299 mVerticalScrollBarMode = msg.arg2; 9300 break; 9301 9302 case SELECTION_STRING_CHANGED: 9303 if (mAccessibilityInjector != null) { 9304 String selectionString = (String) msg.obj; 9305 mAccessibilityInjector.onSelectionStringChange(selectionString); 9306 } 9307 break; 9308 9309 case HIT_TEST_RESULT: 9310 WebKitHitTest hit = (WebKitHitTest) msg.obj; 9311 mFocusedNode = hit; 9312 setTouchHighlightRects(hit); 9313 setHitTestResult(hit); 9314 break; 9315 9316 case SAVE_WEBARCHIVE_FINISHED: 9317 SaveWebArchiveMessage saveMessage = (SaveWebArchiveMessage)msg.obj; 9318 if (saveMessage.mCallback != null) { 9319 saveMessage.mCallback.onReceiveValue(saveMessage.mResultFile); 9320 } 9321 break; 9322 9323 case SET_AUTOFILLABLE: 9324 mAutoFillData = (WebViewCore.AutoFillData) msg.obj; 9325 if (mWebTextView != null) { 9326 mWebTextView.setAutoFillable(mAutoFillData.getQueryId()); 9327 rebuildWebTextView(); 9328 } 9329 break; 9330 9331 case AUTOFILL_COMPLETE: 9332 if (mWebTextView != null) { 9333 // Clear the WebTextView adapter when AutoFill finishes 9334 // so that the drop down gets cleared. 9335 mWebTextView.setAdapterCustom(null); 9336 } 9337 break; 9338 9339 case SELECT_AT: 9340 nativeSelectAt(msg.arg1, msg.arg2); 9341 break; 9342 9343 case COPY_TO_CLIPBOARD: 9344 copyToClipboard((String) msg.obj); 9345 break; 9346 9347 case INIT_EDIT_FIELD: 9348 if (mInputConnection != null) { 9349 TextFieldInitData initData = (TextFieldInitData) msg.obj; 9350 mTextGeneration = 0; 9351 mFieldPointer = initData.mFieldPointer; 9352 mInputConnection.initEditorInfo(initData); 9353 mInputConnection.setTextAndKeepSelection(initData.mText); 9354 } 9355 break; 9356 9357 case REPLACE_TEXT:{ 9358 String text = (String)msg.obj; 9359 int start = msg.arg1; 9360 int end = msg.arg2; 9361 int cursorPosition = start + text.length(); 9362 replaceTextfieldText(start, end, text, 9363 cursorPosition, cursorPosition); 9364 break; 9365 } 9366 9367 case UPDATE_MATCH_COUNT: { 9368 if (mFindCallback != null) { 9369 mFindCallback.updateMatchCount(msg.arg1, msg.arg2, 9370 (String) msg.obj); 9371 } 9372 break; 9373 } 9374 case CLEAR_CARET_HANDLE: 9375 selectionDone(); 9376 break; 9377 9378 case KEY_PRESS: 9379 mWebViewCore.sendMessage(EventHub.KEY_PRESS, msg.arg1); 9380 break; 9381 9382 default: 9383 super.handleMessage(msg); 9384 break; 9385 } 9386 } 9387 } 9388 9389 private void setHitTestTypeFromUrl(String url) { 9390 String substr = null; 9391 if (url.startsWith(SCHEME_GEO)) { 9392 mInitialHitTestResult.mType = HitTestResult.GEO_TYPE; 9393 substr = url.substring(SCHEME_GEO.length()); 9394 } else if (url.startsWith(SCHEME_TEL)) { 9395 mInitialHitTestResult.mType = HitTestResult.PHONE_TYPE; 9396 substr = url.substring(SCHEME_TEL.length()); 9397 } else if (url.startsWith(SCHEME_MAILTO)) { 9398 mInitialHitTestResult.mType = HitTestResult.EMAIL_TYPE; 9399 substr = url.substring(SCHEME_MAILTO.length()); 9400 } else { 9401 mInitialHitTestResult.mType = HitTestResult.SRC_ANCHOR_TYPE; 9402 mInitialHitTestResult.mExtra = url; 9403 return; 9404 } 9405 try { 9406 mInitialHitTestResult.mExtra = URLDecoder.decode(substr, "UTF-8"); 9407 } catch (Throwable e) { 9408 Log.w(LOGTAG, "Failed to decode URL! " + substr, e); 9409 mInitialHitTestResult.mType = HitTestResult.UNKNOWN_TYPE; 9410 } 9411 } 9412 9413 private void setHitTestResult(WebKitHitTest hit) { 9414 if (hit == null) { 9415 mInitialHitTestResult = null; 9416 return; 9417 } 9418 mInitialHitTestResult = new HitTestResult(); 9419 if (hit.mLinkUrl != null) { 9420 setHitTestTypeFromUrl(hit.mLinkUrl); 9421 if (hit.mImageUrl != null 9422 && mInitialHitTestResult.mType == HitTestResult.SRC_ANCHOR_TYPE) { 9423 mInitialHitTestResult.mType = HitTestResult.SRC_IMAGE_ANCHOR_TYPE; 9424 mInitialHitTestResult.mExtra = hit.mImageUrl; 9425 } 9426 } else if (hit.mImageUrl != null) { 9427 mInitialHitTestResult.mType = HitTestResult.IMAGE_TYPE; 9428 mInitialHitTestResult.mExtra = hit.mImageUrl; 9429 } else if (hit.mEditable) { 9430 mInitialHitTestResult.mType = HitTestResult.EDIT_TEXT_TYPE; 9431 } else if (hit.mIntentUrl != null) { 9432 setHitTestTypeFromUrl(hit.mIntentUrl); 9433 } 9434 } 9435 9436 private boolean shouldDrawHighlightRect() { 9437 if (mFocusedNode == null || mInitialHitTestResult == null) { 9438 return false; 9439 } 9440 if (mTouchHighlightRegion.isEmpty()) { 9441 return false; 9442 } 9443 if (mFocusedNode.mHasFocus && !isInTouchMode()) { 9444 return !mFocusedNode.mEditable; 9445 } 9446 if (mInitialHitTestResult.mType == HitTestResult.UNKNOWN_TYPE) { 9447 return false; 9448 } 9449 long delay = System.currentTimeMillis() - mTouchHighlightRequested; 9450 if (delay < ViewConfiguration.getTapTimeout()) { 9451 Rect r = mTouchHighlightRegion.getBounds(); 9452 postInvalidateDelayed(delay, r.left, r.top, r.right, r.bottom); 9453 return false; 9454 } 9455 return true; 9456 } 9457 9458 9459 private FocusTransitionDrawable mFocusTransition = null; 9460 static class FocusTransitionDrawable extends Drawable { 9461 Region mPreviousRegion; 9462 Region mNewRegion; 9463 float mProgress = 0; 9464 WebView mWebView; 9465 Paint mPaint; 9466 int mMaxAlpha; 9467 Point mTranslate; 9468 9469 public FocusTransitionDrawable(WebView view) { 9470 mWebView = view; 9471 mPaint = new Paint(mWebView.mTouchHightlightPaint); 9472 mMaxAlpha = mPaint.getAlpha(); 9473 } 9474 9475 @Override 9476 public void setColorFilter(ColorFilter cf) { 9477 } 9478 9479 @Override 9480 public void setAlpha(int alpha) { 9481 } 9482 9483 @Override 9484 public int getOpacity() { 9485 return 0; 9486 } 9487 9488 public void setProgress(float p) { 9489 mProgress = p; 9490 if (mWebView.mFocusTransition == this) { 9491 if (mProgress == 1f) 9492 mWebView.mFocusTransition = null; 9493 mWebView.invalidate(); 9494 } 9495 } 9496 9497 public float getProgress() { 9498 return mProgress; 9499 } 9500 9501 @Override 9502 public void draw(Canvas canvas) { 9503 if (mTranslate == null) { 9504 Rect bounds = mPreviousRegion.getBounds(); 9505 Point from = new Point(bounds.centerX(), bounds.centerY()); 9506 mNewRegion.getBounds(bounds); 9507 Point to = new Point(bounds.centerX(), bounds.centerY()); 9508 mTranslate = new Point(from.x - to.x, from.y - to.y); 9509 } 9510 int alpha = (int) (mProgress * mMaxAlpha); 9511 RegionIterator iter = new RegionIterator(mPreviousRegion); 9512 Rect r = new Rect(); 9513 mPaint.setAlpha(mMaxAlpha - alpha); 9514 float tx = mTranslate.x * mProgress; 9515 float ty = mTranslate.y * mProgress; 9516 int save = canvas.save(Canvas.MATRIX_SAVE_FLAG); 9517 canvas.translate(-tx, -ty); 9518 while (iter.next(r)) { 9519 canvas.drawRect(r, mPaint); 9520 } 9521 canvas.restoreToCount(save); 9522 iter = new RegionIterator(mNewRegion); 9523 r = new Rect(); 9524 mPaint.setAlpha(alpha); 9525 save = canvas.save(Canvas.MATRIX_SAVE_FLAG); 9526 tx = mTranslate.x - tx; 9527 ty = mTranslate.y - ty; 9528 canvas.translate(tx, ty); 9529 while (iter.next(r)) { 9530 canvas.drawRect(r, mPaint); 9531 } 9532 canvas.restoreToCount(save); 9533 } 9534 }; 9535 9536 private boolean shouldAnimateTo(WebKitHitTest hit) { 9537 // TODO: Don't be annoying or throw out the animation entirely 9538 return false; 9539 } 9540 9541 private void setTouchHighlightRects(WebKitHitTest hit) { 9542 FocusTransitionDrawable transition = null; 9543 if (shouldAnimateTo(hit)) { 9544 transition = new FocusTransitionDrawable(this); 9545 } 9546 Rect[] rects = hit != null ? hit.mTouchRects : null; 9547 if (!mTouchHighlightRegion.isEmpty()) { 9548 invalidate(mTouchHighlightRegion.getBounds()); 9549 if (transition != null) { 9550 transition.mPreviousRegion = new Region(mTouchHighlightRegion); 9551 } 9552 mTouchHighlightRegion.setEmpty(); 9553 } 9554 if (rects != null) { 9555 mTouchHightlightPaint.setColor(hit.mTapHighlightColor); 9556 for (Rect rect : rects) { 9557 Rect viewRect = contentToViewRect(rect); 9558 // some sites, like stories in nytimes.com, set 9559 // mouse event handler in the top div. It is not 9560 // user friendly to highlight the div if it covers 9561 // more than half of the screen. 9562 if (viewRect.width() < getWidth() >> 1 9563 || viewRect.height() < getHeight() >> 1) { 9564 mTouchHighlightRegion.union(viewRect); 9565 } else { 9566 Log.w(LOGTAG, "Skip the huge selection rect:" 9567 + viewRect); 9568 } 9569 } 9570 invalidate(mTouchHighlightRegion.getBounds()); 9571 if (transition != null && transition.mPreviousRegion != null) { 9572 transition.mNewRegion = new Region(mTouchHighlightRegion); 9573 mFocusTransition = transition; 9574 ObjectAnimator animator = ObjectAnimator.ofFloat( 9575 mFocusTransition, "progress", 1f); 9576 animator.start(); 9577 } 9578 } 9579 } 9580 9581 /** @hide Called by JNI when pages are swapped (only occurs with hardware 9582 * acceleration) */ 9583 protected void pageSwapCallback(boolean notifyAnimationStarted) { 9584 mWebViewCore.resumeWebKitDraw(); 9585 if (inEditingMode()) { 9586 didUpdateWebTextViewDimensions(ANYWHERE); 9587 } 9588 if (notifyAnimationStarted) { 9589 mWebViewCore.sendMessage(EventHub.NOTIFY_ANIMATION_STARTED); 9590 } 9591 } 9592 9593 void setNewPicture(final WebViewCore.DrawData draw, boolean updateBaseLayer) { 9594 if (mNativeClass == 0) { 9595 if (mDelaySetPicture != null) { 9596 throw new IllegalStateException("Tried to setNewPicture with" 9597 + " a delay picture already set! (memory leak)"); 9598 } 9599 // Not initialized yet, delay set 9600 mDelaySetPicture = draw; 9601 return; 9602 } 9603 WebViewCore.ViewState viewState = draw.mViewState; 9604 boolean isPictureAfterFirstLayout = viewState != null; 9605 9606 if (updateBaseLayer) { 9607 setBaseLayer(draw.mBaseLayer, draw.mInvalRegion, 9608 getSettings().getShowVisualIndicator(), 9609 isPictureAfterFirstLayout); 9610 } 9611 final Point viewSize = draw.mViewSize; 9612 // We update the layout (i.e. request a layout from the 9613 // view system) if the last view size that we sent to 9614 // WebCore matches the view size of the picture we just 9615 // received in the fixed dimension. 9616 final boolean updateLayout = viewSize.x == mLastWidthSent 9617 && viewSize.y == mLastHeightSent; 9618 // Don't send scroll event for picture coming from webkit, 9619 // since the new picture may cause a scroll event to override 9620 // the saved history scroll position. 9621 mSendScrollEvent = false; 9622 recordNewContentSize(draw.mContentSize.x, 9623 draw.mContentSize.y, updateLayout); 9624 if (isPictureAfterFirstLayout) { 9625 // Reset the last sent data here since dealing with new page. 9626 mLastWidthSent = 0; 9627 mZoomManager.onFirstLayout(draw); 9628 int scrollX = viewState.mShouldStartScrolledRight 9629 ? getContentWidth() : viewState.mScrollX; 9630 int scrollY = viewState.mScrollY; 9631 setContentScrollTo(scrollX, scrollY); 9632 if (!mDrawHistory) { 9633 // As we are on a new page, remove the WebTextView. This 9634 // is necessary for page loads driven by webkit, and in 9635 // particular when the user was on a password field, so 9636 // the WebTextView was visible. 9637 clearTextEntry(); 9638 } 9639 } 9640 mSendScrollEvent = true; 9641 9642 if (DebugFlags.WEB_VIEW) { 9643 Rect b = draw.mInvalRegion.getBounds(); 9644 Log.v(LOGTAG, "NEW_PICTURE_MSG_ID {" + 9645 b.left+","+b.top+","+b.right+","+b.bottom+"}"); 9646 } 9647 invalidateContentRect(draw.mInvalRegion.getBounds()); 9648 9649 if (mPictureListener != null) { 9650 mPictureListener.onNewPicture(WebView.this, capturePicture()); 9651 } 9652 9653 // update the zoom information based on the new picture 9654 mZoomManager.onNewPicture(draw); 9655 9656 if (draw.mFocusSizeChanged && inEditingMode()) { 9657 mFocusSizeChanged = true; 9658 } 9659 if (isPictureAfterFirstLayout) { 9660 mViewManager.postReadyToDrawAll(); 9661 } 9662 } 9663 9664 /** 9665 * Used when receiving messages for REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID 9666 * and UPDATE_TEXT_SELECTION_MSG_ID. Update the selection of WebTextView. 9667 */ 9668 private void updateTextSelectionFromMessage(int nodePointer, 9669 int textGeneration, WebViewCore.TextSelectionData data) { 9670 if (textGeneration == mTextGeneration) { 9671 if (inEditingMode() 9672 && mWebTextView.isSameTextField(nodePointer)) { 9673 mWebTextView.setSelectionFromWebKit(data.mStart, data.mEnd); 9674 } else if (mInputConnection != null && mFieldPointer == nodePointer) { 9675 mInputConnection.setSelection(data.mStart, data.mEnd); 9676 } 9677 } 9678 nativeSetTextSelection(mNativeClass, data.mSelectTextPtr); 9679 9680 if (data.mSelectTextPtr != 0 && 9681 (data.mStart != data.mEnd || 9682 (mFieldPointer == nodePointer && mFieldPointer != 0))) { 9683 mIsCaretSelection = (data.mStart == data.mEnd); 9684 if (!mSelectingText) { 9685 setupWebkitSelect(); 9686 } else if (!mSelectionStarted) { 9687 syncSelectionCursors(); 9688 } 9689 if (mIsCaretSelection) { 9690 resetCaretTimer(); 9691 } 9692 } else { 9693 selectionDone(); 9694 } 9695 invalidate(); 9696 } 9697 9698 // Class used to use a dropdown for a <select> element 9699 private class InvokeListBox implements Runnable { 9700 // Whether the listbox allows multiple selection. 9701 private boolean mMultiple; 9702 // Passed in to a list with multiple selection to tell 9703 // which items are selected. 9704 private int[] mSelectedArray; 9705 // Passed in to a list with single selection to tell 9706 // where the initial selection is. 9707 private int mSelection; 9708 9709 private Container[] mContainers; 9710 9711 // Need these to provide stable ids to my ArrayAdapter, 9712 // which normally does not have stable ids. (Bug 1250098) 9713 private class Container extends Object { 9714 /** 9715 * Possible values for mEnabled. Keep in sync with OptionStatus in 9716 * WebViewCore.cpp 9717 */ 9718 final static int OPTGROUP = -1; 9719 final static int OPTION_DISABLED = 0; 9720 final static int OPTION_ENABLED = 1; 9721 9722 String mString; 9723 int mEnabled; 9724 int mId; 9725 9726 @Override 9727 public String toString() { 9728 return mString; 9729 } 9730 } 9731 9732 /** 9733 * Subclass ArrayAdapter so we can disable OptionGroupLabels, 9734 * and allow filtering. 9735 */ 9736 private class MyArrayListAdapter extends ArrayAdapter<Container> { 9737 public MyArrayListAdapter() { 9738 super(mContext, 9739 mMultiple ? com.android.internal.R.layout.select_dialog_multichoice : 9740 com.android.internal.R.layout.webview_select_singlechoice, 9741 mContainers); 9742 } 9743 9744 @Override 9745 public View getView(int position, View convertView, 9746 ViewGroup parent) { 9747 // Always pass in null so that we will get a new CheckedTextView 9748 // Otherwise, an item which was previously used as an <optgroup> 9749 // element (i.e. has no check), could get used as an <option> 9750 // element, which needs a checkbox/radio, but it would not have 9751 // one. 9752 convertView = super.getView(position, null, parent); 9753 Container c = item(position); 9754 if (c != null && Container.OPTION_ENABLED != c.mEnabled) { 9755 // ListView does not draw dividers between disabled and 9756 // enabled elements. Use a LinearLayout to provide dividers 9757 LinearLayout layout = new LinearLayout(mContext); 9758 layout.setOrientation(LinearLayout.VERTICAL); 9759 if (position > 0) { 9760 View dividerTop = new View(mContext); 9761 dividerTop.setBackgroundResource( 9762 android.R.drawable.divider_horizontal_bright); 9763 layout.addView(dividerTop); 9764 } 9765 9766 if (Container.OPTGROUP == c.mEnabled) { 9767 // Currently select_dialog_multichoice uses CheckedTextViews. 9768 // If that changes, the class cast will no longer be valid. 9769 if (mMultiple) { 9770 Assert.assertTrue(convertView instanceof CheckedTextView); 9771 ((CheckedTextView) convertView).setCheckMarkDrawable(null); 9772 } 9773 } else { 9774 // c.mEnabled == Container.OPTION_DISABLED 9775 // Draw the disabled element in a disabled state. 9776 convertView.setEnabled(false); 9777 } 9778 9779 layout.addView(convertView); 9780 if (position < getCount() - 1) { 9781 View dividerBottom = new View(mContext); 9782 dividerBottom.setBackgroundResource( 9783 android.R.drawable.divider_horizontal_bright); 9784 layout.addView(dividerBottom); 9785 } 9786 return layout; 9787 } 9788 return convertView; 9789 } 9790 9791 @Override 9792 public boolean hasStableIds() { 9793 // AdapterView's onChanged method uses this to determine whether 9794 // to restore the old state. Return false so that the old (out 9795 // of date) state does not replace the new, valid state. 9796 return false; 9797 } 9798 9799 private Container item(int position) { 9800 if (position < 0 || position >= getCount()) { 9801 return null; 9802 } 9803 return getItem(position); 9804 } 9805 9806 @Override 9807 public long getItemId(int position) { 9808 Container item = item(position); 9809 if (item == null) { 9810 return -1; 9811 } 9812 return item.mId; 9813 } 9814 9815 @Override 9816 public boolean areAllItemsEnabled() { 9817 return false; 9818 } 9819 9820 @Override 9821 public boolean isEnabled(int position) { 9822 Container item = item(position); 9823 if (item == null) { 9824 return false; 9825 } 9826 return Container.OPTION_ENABLED == item.mEnabled; 9827 } 9828 } 9829 9830 private InvokeListBox(String[] array, int[] enabled, int[] selected) { 9831 mMultiple = true; 9832 mSelectedArray = selected; 9833 9834 int length = array.length; 9835 mContainers = new Container[length]; 9836 for (int i = 0; i < length; i++) { 9837 mContainers[i] = new Container(); 9838 mContainers[i].mString = array[i]; 9839 mContainers[i].mEnabled = enabled[i]; 9840 mContainers[i].mId = i; 9841 } 9842 } 9843 9844 private InvokeListBox(String[] array, int[] enabled, int selection) { 9845 mSelection = selection; 9846 mMultiple = false; 9847 9848 int length = array.length; 9849 mContainers = new Container[length]; 9850 for (int i = 0; i < length; i++) { 9851 mContainers[i] = new Container(); 9852 mContainers[i].mString = array[i]; 9853 mContainers[i].mEnabled = enabled[i]; 9854 mContainers[i].mId = i; 9855 } 9856 } 9857 9858 /* 9859 * Whenever the data set changes due to filtering, this class ensures 9860 * that the checked item remains checked. 9861 */ 9862 private class SingleDataSetObserver extends DataSetObserver { 9863 private long mCheckedId; 9864 private ListView mListView; 9865 private Adapter mAdapter; 9866 9867 /* 9868 * Create a new observer. 9869 * @param id The ID of the item to keep checked. 9870 * @param l ListView for getting and clearing the checked states 9871 * @param a Adapter for getting the IDs 9872 */ 9873 public SingleDataSetObserver(long id, ListView l, Adapter a) { 9874 mCheckedId = id; 9875 mListView = l; 9876 mAdapter = a; 9877 } 9878 9879 @Override 9880 public void onChanged() { 9881 // The filter may have changed which item is checked. Find the 9882 // item that the ListView thinks is checked. 9883 int position = mListView.getCheckedItemPosition(); 9884 long id = mAdapter.getItemId(position); 9885 if (mCheckedId != id) { 9886 // Clear the ListView's idea of the checked item, since 9887 // it is incorrect 9888 mListView.clearChoices(); 9889 // Search for mCheckedId. If it is in the filtered list, 9890 // mark it as checked 9891 int count = mAdapter.getCount(); 9892 for (int i = 0; i < count; i++) { 9893 if (mAdapter.getItemId(i) == mCheckedId) { 9894 mListView.setItemChecked(i, true); 9895 break; 9896 } 9897 } 9898 } 9899 } 9900 } 9901 9902 @Override 9903 public void run() { 9904 final ListView listView = (ListView) LayoutInflater.from(mContext) 9905 .inflate(com.android.internal.R.layout.select_dialog, null); 9906 final MyArrayListAdapter adapter = new MyArrayListAdapter(); 9907 AlertDialog.Builder b = new AlertDialog.Builder(mContext) 9908 .setView(listView).setCancelable(true) 9909 .setInverseBackgroundForced(true); 9910 9911 if (mMultiple) { 9912 b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 9913 @Override 9914 public void onClick(DialogInterface dialog, int which) { 9915 mWebViewCore.sendMessage( 9916 EventHub.LISTBOX_CHOICES, 9917 adapter.getCount(), 0, 9918 listView.getCheckedItemPositions()); 9919 }}); 9920 b.setNegativeButton(android.R.string.cancel, 9921 new DialogInterface.OnClickListener() { 9922 @Override 9923 public void onClick(DialogInterface dialog, int which) { 9924 mWebViewCore.sendMessage( 9925 EventHub.SINGLE_LISTBOX_CHOICE, -2, 0); 9926 }}); 9927 } 9928 mListBoxDialog = b.create(); 9929 listView.setAdapter(adapter); 9930 listView.setFocusableInTouchMode(true); 9931 // There is a bug (1250103) where the checks in a ListView with 9932 // multiple items selected are associated with the positions, not 9933 // the ids, so the items do not properly retain their checks when 9934 // filtered. Do not allow filtering on multiple lists until 9935 // that bug is fixed. 9936 9937 listView.setTextFilterEnabled(!mMultiple); 9938 if (mMultiple) { 9939 listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); 9940 int length = mSelectedArray.length; 9941 for (int i = 0; i < length; i++) { 9942 listView.setItemChecked(mSelectedArray[i], true); 9943 } 9944 } else { 9945 listView.setOnItemClickListener(new OnItemClickListener() { 9946 @Override 9947 public void onItemClick(AdapterView<?> parent, View v, 9948 int position, long id) { 9949 // Rather than sending the message right away, send it 9950 // after the page regains focus. 9951 mListBoxMessage = Message.obtain(null, 9952 EventHub.SINGLE_LISTBOX_CHOICE, (int) id, 0); 9953 mListBoxDialog.dismiss(); 9954 mListBoxDialog = null; 9955 } 9956 }); 9957 if (mSelection != -1) { 9958 listView.setSelection(mSelection); 9959 listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 9960 listView.setItemChecked(mSelection, true); 9961 DataSetObserver observer = new SingleDataSetObserver( 9962 adapter.getItemId(mSelection), listView, adapter); 9963 adapter.registerDataSetObserver(observer); 9964 } 9965 } 9966 mListBoxDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { 9967 @Override 9968 public void onCancel(DialogInterface dialog) { 9969 mWebViewCore.sendMessage( 9970 EventHub.SINGLE_LISTBOX_CHOICE, -2, 0); 9971 mListBoxDialog = null; 9972 } 9973 }); 9974 mListBoxDialog.show(); 9975 } 9976 } 9977 9978 private Message mListBoxMessage; 9979 9980 /* 9981 * Request a dropdown menu for a listbox with multiple selection. 9982 * 9983 * @param array Labels for the listbox. 9984 * @param enabledArray State for each element in the list. See static 9985 * integers in Container class. 9986 * @param selectedArray Which positions are initally selected. 9987 */ 9988 void requestListBox(String[] array, int[] enabledArray, int[] 9989 selectedArray) { 9990 mPrivateHandler.post( 9991 new InvokeListBox(array, enabledArray, selectedArray)); 9992 } 9993 9994 /* 9995 * Request a dropdown menu for a listbox with single selection or a single 9996 * <select> element. 9997 * 9998 * @param array Labels for the listbox. 9999 * @param enabledArray State for each element in the list. See static 10000 * integers in Container class. 10001 * @param selection Which position is initally selected. 10002 */ 10003 void requestListBox(String[] array, int[] enabledArray, int selection) { 10004 mPrivateHandler.post( 10005 new InvokeListBox(array, enabledArray, selection)); 10006 } 10007 10008 // called by JNI 10009 private void sendMoveFocus(int frame, int node) { 10010 mWebViewCore.sendMessage(EventHub.SET_MOVE_FOCUS, 10011 new WebViewCore.CursorData(frame, node, 0, 0)); 10012 } 10013 10014 // called by JNI 10015 private void sendMoveMouse(int frame, int node, int x, int y) { 10016 mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, 10017 new WebViewCore.CursorData(frame, node, x, y)); 10018 } 10019 10020 /* 10021 * Send a mouse move event to the webcore thread. 10022 * 10023 * @param removeFocus Pass true to remove the WebTextView, if present. 10024 * @param stopPaintingCaret Stop drawing the blinking caret if true. 10025 * called by JNI 10026 */ 10027 @SuppressWarnings("unused") 10028 private void sendMoveMouseIfLatest(boolean removeFocus, boolean stopPaintingCaret) { 10029 if (removeFocus) { 10030 clearTextEntry(); 10031 } 10032 mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE_IF_LATEST, 10033 stopPaintingCaret ? 1 : 0, 0, 10034 cursorData()); 10035 } 10036 10037 /** 10038 * Called by JNI to send a message to the webcore thread that the user 10039 * touched the webpage. 10040 * @param touchGeneration Generation number of the touch, to ignore touches 10041 * after a new one has been generated. 10042 * @param frame Pointer to the frame holding the node that was touched. 10043 * @param node Pointer to the node touched. 10044 * @param x x-position of the touch. 10045 * @param y y-position of the touch. 10046 */ 10047 private void sendMotionUp(int touchGeneration, 10048 int frame, int node, int x, int y) { 10049 WebViewCore.TouchUpData touchUpData = new WebViewCore.TouchUpData(); 10050 touchUpData.mMoveGeneration = touchGeneration; 10051 touchUpData.mFrame = frame; 10052 touchUpData.mNode = node; 10053 touchUpData.mX = x; 10054 touchUpData.mY = y; 10055 touchUpData.mNativeLayer = nativeScrollableLayer( 10056 x, y, touchUpData.mNativeLayerRect, null); 10057 mWebViewCore.sendMessage(EventHub.TOUCH_UP, touchUpData); 10058 } 10059 10060 10061 private int getScaledMaxXScroll() { 10062 int width; 10063 if (mHeightCanMeasure == false) { 10064 width = getViewWidth() / 4; 10065 } else { 10066 Rect visRect = new Rect(); 10067 calcOurVisibleRect(visRect); 10068 width = visRect.width() / 2; 10069 } 10070 // FIXME the divisor should be retrieved from somewhere 10071 return viewToContentX(width); 10072 } 10073 10074 private int getScaledMaxYScroll() { 10075 int height; 10076 if (mHeightCanMeasure == false) { 10077 height = getViewHeight() / 4; 10078 } else { 10079 Rect visRect = new Rect(); 10080 calcOurVisibleRect(visRect); 10081 height = visRect.height() / 2; 10082 } 10083 // FIXME the divisor should be retrieved from somewhere 10084 // the closest thing today is hard-coded into ScrollView.java 10085 // (from ScrollView.java, line 363) int maxJump = height/2; 10086 return Math.round(height * mZoomManager.getInvScale()); 10087 } 10088 10089 /** 10090 * Called by JNI to invalidate view 10091 */ 10092 private void viewInvalidate() { 10093 invalidate(); 10094 } 10095 10096 /** 10097 * Pass the key directly to the page. This assumes that 10098 * nativePageShouldHandleShiftAndArrows() returned true. 10099 */ 10100 private void letPageHandleNavKey(int keyCode, long time, boolean down, int metaState) { 10101 int keyEventAction; 10102 int eventHubAction; 10103 if (down) { 10104 keyEventAction = KeyEvent.ACTION_DOWN; 10105 eventHubAction = EventHub.KEY_DOWN; 10106 playSoundEffect(keyCodeToSoundsEffect(keyCode)); 10107 } else { 10108 keyEventAction = KeyEvent.ACTION_UP; 10109 eventHubAction = EventHub.KEY_UP; 10110 } 10111 10112 KeyEvent event = new KeyEvent(time, time, keyEventAction, keyCode, 10113 1, (metaState & KeyEvent.META_SHIFT_ON) 10114 | (metaState & KeyEvent.META_ALT_ON) 10115 | (metaState & KeyEvent.META_SYM_ON) 10116 , KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0); 10117 mWebViewCore.sendMessage(eventHubAction, event); 10118 } 10119 10120 // return true if the key was handled 10121 private boolean navHandledKey(int keyCode, int count, boolean noScroll, 10122 long time) { 10123 if (mNativeClass == 0) { 10124 return false; 10125 } 10126 mInitialHitTestResult = null; 10127 mLastCursorTime = time; 10128 mLastCursorBounds = nativeGetCursorRingBounds(); 10129 boolean keyHandled 10130 = nativeMoveCursor(keyCode, count, noScroll) == false; 10131 if (DebugFlags.WEB_VIEW) { 10132 Log.v(LOGTAG, "navHandledKey mLastCursorBounds=" + mLastCursorBounds 10133 + " mLastCursorTime=" + mLastCursorTime 10134 + " handled=" + keyHandled); 10135 } 10136 if (keyHandled == false) { 10137 return keyHandled; 10138 } 10139 Rect contentCursorRingBounds = nativeGetCursorRingBounds(); 10140 if (contentCursorRingBounds.isEmpty()) return keyHandled; 10141 Rect viewCursorRingBounds = contentToViewRect(contentCursorRingBounds); 10142 // set last touch so that context menu related functions will work 10143 mLastTouchX = (viewCursorRingBounds.left + viewCursorRingBounds.right) / 2; 10144 mLastTouchY = (viewCursorRingBounds.top + viewCursorRingBounds.bottom) / 2; 10145 if (mHeightCanMeasure == false) { 10146 return keyHandled; 10147 } 10148 Rect visRect = new Rect(); 10149 calcOurVisibleRect(visRect); 10150 Rect outset = new Rect(visRect); 10151 int maxXScroll = visRect.width() / 2; 10152 int maxYScroll = visRect.height() / 2; 10153 outset.inset(-maxXScroll, -maxYScroll); 10154 if (Rect.intersects(outset, viewCursorRingBounds) == false) { 10155 return keyHandled; 10156 } 10157 // FIXME: Necessary because ScrollView/ListView do not scroll left/right 10158 int maxH = Math.min(viewCursorRingBounds.right - visRect.right, 10159 maxXScroll); 10160 if (maxH > 0) { 10161 pinScrollBy(maxH, 0, true, 0); 10162 } else { 10163 maxH = Math.max(viewCursorRingBounds.left - visRect.left, 10164 -maxXScroll); 10165 if (maxH < 0) { 10166 pinScrollBy(maxH, 0, true, 0); 10167 } 10168 } 10169 if (mLastCursorBounds.isEmpty()) return keyHandled; 10170 if (mLastCursorBounds.equals(contentCursorRingBounds)) { 10171 return keyHandled; 10172 } 10173 if (DebugFlags.WEB_VIEW) { 10174 Log.v(LOGTAG, "navHandledKey contentCursorRingBounds=" 10175 + contentCursorRingBounds); 10176 } 10177 requestRectangleOnScreen(viewCursorRingBounds); 10178 return keyHandled; 10179 } 10180 10181 /** 10182 * @return Whether accessibility script has been injected. 10183 */ 10184 private boolean accessibilityScriptInjected() { 10185 // TODO: Maybe the injected script should announce its presence in 10186 // the page meta-tag so the nativePageShouldHandleShiftAndArrows 10187 // will check that as one of the conditions it looks for 10188 return mAccessibilityScriptInjected; 10189 } 10190 10191 /** 10192 * Set the background color. It's white by default. Pass 10193 * zero to make the view transparent. 10194 * @param color the ARGB color described by Color.java 10195 */ 10196 @Override 10197 public void setBackgroundColor(int color) { 10198 mBackgroundColor = color; 10199 mWebViewCore.sendMessage(EventHub.SET_BACKGROUND_COLOR, color); 10200 } 10201 10202 /** 10203 * @deprecated This method is now obsolete. 10204 */ 10205 @Deprecated 10206 public void debugDump() { 10207 checkThread(); 10208 nativeDebugDump(); 10209 mWebViewCore.sendMessage(EventHub.DUMP_NAVTREE); 10210 } 10211 10212 /** 10213 * Draw the HTML page into the specified canvas. This call ignores any 10214 * view-specific zoom, scroll offset, or other changes. It does not draw 10215 * any view-specific chrome, such as progress or URL bars. 10216 * 10217 * @hide only needs to be accessible to Browser and testing 10218 */ 10219 public void drawPage(Canvas canvas) { 10220 calcOurContentVisibleRectF(mVisibleContentRect); 10221 nativeDraw(canvas, mVisibleContentRect, 0, 0, false); 10222 } 10223 10224 /** 10225 * Enable the communication b/t the webView and VideoViewProxy 10226 * 10227 * @hide only used by the Browser 10228 */ 10229 public void setHTML5VideoViewProxy(HTML5VideoViewProxy proxy) { 10230 mHTML5VideoViewProxy = proxy; 10231 } 10232 10233 /** 10234 * Set the time to wait between passing touches to WebCore. See also the 10235 * TOUCH_SENT_INTERVAL member for further discussion. 10236 * 10237 * @hide This is only used by the DRT test application. 10238 */ 10239 public void setTouchInterval(int interval) { 10240 mCurrentTouchInterval = interval; 10241 } 10242 10243 /** 10244 * Copy text into the clipboard. This is called indirectly from 10245 * WebViewCore. 10246 * @param text The text to put into the clipboard. 10247 */ 10248 private void copyToClipboard(String text) { 10249 ClipboardManager cm = (ClipboardManager)getContext() 10250 .getSystemService(Context.CLIPBOARD_SERVICE); 10251 ClipData clip = ClipData.newPlainText(getTitle(), text); 10252 cm.setPrimaryClip(clip); 10253 } 10254 10255 /** 10256 * Update our cache with updatedText. 10257 * @param updatedText The new text to put in our cache. 10258 * @hide 10259 */ 10260 protected void updateCachedTextfield(String updatedText) { 10261 // Also place our generation number so that when we look at the cache 10262 // we recognize that it is up to date. 10263 nativeUpdateCachedTextfield(updatedText, mTextGeneration); 10264 } 10265 10266 /*package*/ void autoFillForm(int autoFillQueryId) { 10267 mWebViewCore.sendMessage(EventHub.AUTOFILL_FORM, autoFillQueryId, /* unused */0); 10268 } 10269 10270 /* package */ ViewManager getViewManager() { 10271 return mViewManager; 10272 } 10273 10274 private static void checkThread() { 10275 if (Looper.myLooper() != Looper.getMainLooper()) { 10276 Throwable throwable = new Throwable( 10277 "Warning: A WebView method was called on thread '" + 10278 Thread.currentThread().getName() + "'. " + 10279 "All WebView methods must be called on the UI thread. " + 10280 "Future versions of WebView may not support use on other threads."); 10281 Log.w(LOGTAG, Log.getStackTraceString(throwable)); 10282 StrictMode.onWebViewMethodCalledOnWrongThread(throwable); 10283 } 10284 } 10285 10286 /** @hide send content invalidate */ 10287 protected void contentInvalidateAll() { 10288 if (mWebViewCore != null && !mBlockWebkitViewMessages) { 10289 mWebViewCore.sendMessage(EventHub.CONTENT_INVALIDATE_ALL); 10290 } 10291 } 10292 10293 /** @hide discard all textures from tiles */ 10294 protected void discardAllTextures() { 10295 nativeDiscardAllTextures(); 10296 } 10297 10298 /** 10299 * Begin collecting per-tile profiling data 10300 * 10301 * @hide only used by profiling tests 10302 */ 10303 public void tileProfilingStart() { 10304 nativeTileProfilingStart(); 10305 } 10306 /** 10307 * Return per-tile profiling data 10308 * 10309 * @hide only used by profiling tests 10310 */ 10311 public float tileProfilingStop() { 10312 return nativeTileProfilingStop(); 10313 } 10314 10315 /** @hide only used by profiling tests */ 10316 public void tileProfilingClear() { 10317 nativeTileProfilingClear(); 10318 } 10319 /** @hide only used by profiling tests */ 10320 public int tileProfilingNumFrames() { 10321 return nativeTileProfilingNumFrames(); 10322 } 10323 /** @hide only used by profiling tests */ 10324 public int tileProfilingNumTilesInFrame(int frame) { 10325 return nativeTileProfilingNumTilesInFrame(frame); 10326 } 10327 /** @hide only used by profiling tests */ 10328 public int tileProfilingGetInt(int frame, int tile, String key) { 10329 return nativeTileProfilingGetInt(frame, tile, key); 10330 } 10331 /** @hide only used by profiling tests */ 10332 public float tileProfilingGetFloat(int frame, int tile, String key) { 10333 return nativeTileProfilingGetFloat(frame, tile, key); 10334 } 10335 10336 /** 10337 * Checks the focused content for an editable text field. This can be 10338 * text input or ContentEditable. 10339 * @return true if the focused item is an editable text field. 10340 */ 10341 boolean focusCandidateIsEditableText() { 10342 boolean isEditable = false; 10343 // TODO: reverse sDisableNavcache so that its name is positive 10344 boolean isNavcacheEnabled = !sDisableNavcache; 10345 if (isNavcacheEnabled) { 10346 isEditable = nativeFocusCandidateIsEditableText(mNativeClass); 10347 } else if (mFocusedNode != null) { 10348 isEditable = mFocusedNode.mEditable; 10349 } 10350 return isEditable; 10351 } 10352 10353 private native int nativeCacheHitFramePointer(); 10354 private native boolean nativeCacheHitIsPlugin(); 10355 private native Rect nativeCacheHitNodeBounds(); 10356 private native int nativeCacheHitNodePointer(); 10357 /* package */ native void nativeClearCursor(); 10358 private native void nativeCreate(int ptr, String drawableDir, boolean isHighEndGfx); 10359 private native int nativeCursorFramePointer(); 10360 private native Rect nativeCursorNodeBounds(); 10361 private native int nativeCursorNodePointer(); 10362 private native boolean nativeCursorIntersects(Rect visibleRect); 10363 private native boolean nativeCursorIsAnchor(); 10364 private native boolean nativeCursorIsTextInput(); 10365 private native Point nativeCursorPosition(); 10366 private native String nativeCursorText(); 10367 /** 10368 * Returns true if the native cursor node says it wants to handle key events 10369 * (ala plugins). This can only be called if mNativeClass is non-zero! 10370 */ 10371 private native boolean nativeCursorWantsKeyEvents(); 10372 private native void nativeDebugDump(); 10373 private native void nativeDestroy(); 10374 10375 /** 10376 * Draw the picture set with a background color and extra. If 10377 * "splitIfNeeded" is true and the return value is not 0, the return value 10378 * MUST be passed to WebViewCore with SPLIT_PICTURE_SET message so that the 10379 * native allocation can be freed. 10380 */ 10381 private native int nativeDraw(Canvas canvas, RectF visibleRect, 10382 int color, int extra, boolean splitIfNeeded); 10383 private native void nativeDumpDisplayTree(String urlOrNull); 10384 private native boolean nativeEvaluateLayersAnimations(int nativeInstance); 10385 private native int nativeGetDrawGLFunction(int nativeInstance, Rect rect, 10386 Rect viewRect, RectF visibleRect, float scale, int extras); 10387 private native void nativeUpdateDrawGLFunction(Rect rect, Rect viewRect, 10388 RectF visibleRect, float scale); 10389 private native void nativeExtendSelection(int x, int y); 10390 /* package */ native int nativeFocusCandidateFramePointer(); 10391 /* package */ native boolean nativeFocusCandidateHasNextTextfield(); 10392 /* package */ native boolean nativeFocusCandidateIsPassword(); 10393 private native boolean nativeFocusCandidateIsRtlText(); 10394 private native boolean nativeFocusCandidateIsTextInput(); 10395 private native boolean nativeFocusCandidateIsEditableText(int nativeClass); 10396 /* package */ native int nativeFocusCandidateMaxLength(); 10397 /* package */ native boolean nativeFocusCandidateIsAutoComplete(); 10398 /* package */ native boolean nativeFocusCandidateIsSpellcheck(); 10399 /* package */ native String nativeFocusCandidateName(); 10400 private native Rect nativeFocusCandidateNodeBounds(); 10401 /** 10402 * @return A Rect with left, top, right, bottom set to the corresponding 10403 * padding values in the focus candidate, if it is a textfield/textarea with 10404 * a style. Otherwise return null. This is not actually a rectangle; Rect 10405 * is being used to pass four integers. 10406 */ 10407 private native Rect nativeFocusCandidatePaddingRect(); 10408 /* package */ native int nativeFocusCandidatePointer(); 10409 private native String nativeFocusCandidateText(); 10410 /* package */ native float nativeFocusCandidateTextSize(); 10411 /* package */ native int nativeFocusCandidateLineHeight(); 10412 /** 10413 * Returns an integer corresponding to WebView.cpp::type. 10414 * See WebTextView.setType() 10415 */ 10416 private native int nativeFocusCandidateType(); 10417 private native int nativeFocusCandidateLayerId(); 10418 private native boolean nativeFocusIsPlugin(); 10419 private native Rect nativeFocusNodeBounds(); 10420 /* package */ native int nativeFocusNodePointer(); 10421 private native Rect nativeGetCursorRingBounds(); 10422 private native String nativeGetSelection(); 10423 private native boolean nativeHasCursorNode(); 10424 private native boolean nativeHasFocusNode(); 10425 private native void nativeHideCursor(); 10426 private native boolean nativeHitSelection(int x, int y); 10427 private native String nativeImageURI(int x, int y); 10428 private native Rect nativeLayerBounds(int layer); 10429 /* package */ native boolean nativeMoveCursorToNextTextInput(); 10430 // return true if the page has been scrolled 10431 private native boolean nativeMotionUp(int x, int y, int slop); 10432 // returns false if it handled the key 10433 private native boolean nativeMoveCursor(int keyCode, int count, 10434 boolean noScroll); 10435 private native int nativeMoveGeneration(); 10436 /** 10437 * @return true if the page should get the shift and arrow keys, rather 10438 * than select text/navigation. 10439 * 10440 * If the focus is a plugin, or if the focus and cursor match and are 10441 * a contentEditable element, then the page should handle these keys. 10442 */ 10443 private native boolean nativePageShouldHandleShiftAndArrows(); 10444 private native boolean nativePointInNavCache(int x, int y, int slop); 10445 private native void nativeSelectBestAt(Rect rect); 10446 private native void nativeSelectAt(int x, int y); 10447 private native void nativeSetExtendSelection(); 10448 private native void nativeSetFindIsUp(boolean isUp); 10449 private native void nativeSetHeightCanMeasure(boolean measure); 10450 private native boolean nativeSetBaseLayer(int nativeInstance, 10451 int layer, Region invalRegion, 10452 boolean showVisualIndicator, boolean isPictureAfterFirstLayout); 10453 private native int nativeGetBaseLayer(); 10454 private native void nativeShowCursorTimed(); 10455 private native void nativeReplaceBaseContent(int content); 10456 private native void nativeCopyBaseContentToPicture(Picture pict); 10457 private native boolean nativeHasContent(); 10458 private native void nativeSetSelectionPointer(int nativeInstance, 10459 boolean set, float scale, int x, int y); 10460 private native boolean nativeStartSelection(int x, int y); 10461 private native void nativeStopGL(); 10462 private native Rect nativeSubtractLayers(Rect content); 10463 private native int nativeTextGeneration(); 10464 private native void nativeDiscardAllTextures(); 10465 private native void nativeTileProfilingStart(); 10466 private native float nativeTileProfilingStop(); 10467 private native void nativeTileProfilingClear(); 10468 private native int nativeTileProfilingNumFrames(); 10469 private native int nativeTileProfilingNumTilesInFrame(int frame); 10470 private native int nativeTileProfilingGetInt(int frame, int tile, String key); 10471 private native float nativeTileProfilingGetFloat(int frame, int tile, String key); 10472 // Never call this version except by updateCachedTextfield(String) - 10473 // we always want to pass in our generation number. 10474 private native void nativeUpdateCachedTextfield(String updatedText, 10475 int generation); 10476 private native boolean nativeWordSelection(int x, int y); 10477 // return NO_LEFTEDGE means failure. 10478 static final int NO_LEFTEDGE = -1; 10479 native int nativeGetBlockLeftEdge(int x, int y, float scale); 10480 10481 private native void nativeUseHardwareAccelSkia(boolean enabled); 10482 10483 // Returns a pointer to the scrollable LayerAndroid at the given point. 10484 private native int nativeScrollableLayer(int x, int y, Rect scrollRect, 10485 Rect scrollBounds); 10486 /** 10487 * Scroll the specified layer. 10488 * @param layer Id of the layer to scroll, as determined by nativeScrollableLayer. 10489 * @param newX Destination x position to which to scroll. 10490 * @param newY Destination y position to which to scroll. 10491 * @return True if the layer is successfully scrolled. 10492 */ 10493 private native boolean nativeScrollLayer(int layer, int newX, int newY); 10494 private native void nativeSetIsScrolling(boolean isScrolling); 10495 private native int nativeGetBackgroundColor(); 10496 native boolean nativeSetProperty(String key, String value); 10497 native String nativeGetProperty(String key); 10498 /** 10499 * See {@link ComponentCallbacks2} for the trim levels and descriptions 10500 */ 10501 private static native void nativeOnTrimMemory(int level); 10502 private static native void nativeSetPauseDrawing(int instance, boolean pause); 10503 private static native boolean nativeDisableNavcache(); 10504 private static native void nativeSetTextSelection(int instance, int selection); 10505 private static native int nativeGetHandleLayerId(int instance, int handle, 10506 Rect cursorLocation); 10507 private static native boolean nativeIsBaseFirst(int instance); 10508} 10509