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