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