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