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