WebView.java revision adcd2ed8d24deddee528e96260d0ed673eeb261c
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.app.AlertDialog; 20import android.content.Context; 21import android.content.DialogInterface; 22import android.content.Intent; 23import android.content.DialogInterface.OnCancelListener; 24import android.content.res.TypedArray; 25import android.graphics.Bitmap; 26import android.graphics.Canvas; 27import android.graphics.Color; 28import android.graphics.Paint; 29import android.graphics.Path; 30import android.graphics.Picture; 31import android.graphics.Point; 32import android.graphics.Rect; 33import android.graphics.Region; 34import android.net.http.SslCertificate; 35import android.net.Uri; 36import android.os.Bundle; 37import android.os.Handler; 38import android.os.Message; 39import android.os.ServiceManager; 40import android.os.SystemClock; 41import android.text.IClipboard; 42import android.text.Selection; 43import android.text.Spannable; 44import android.util.AttributeSet; 45import android.util.Config; 46import android.util.Log; 47import android.view.animation.AlphaAnimation; 48import android.view.Gravity; 49import android.view.KeyEvent; 50import android.view.LayoutInflater; 51import android.view.MotionEvent; 52import android.view.SoundEffectConstants; 53import android.view.VelocityTracker; 54import android.view.View; 55import android.view.ViewConfiguration; 56import android.view.ViewGroup; 57import android.view.ViewParent; 58import android.view.ViewTreeObserver; 59import android.view.inputmethod.InputMethodManager; 60import android.webkit.TextDialog.AutoCompleteAdapter; 61import android.webkit.WebViewCore.EventHub; 62import android.widget.AbsoluteLayout; 63import android.widget.AdapterView; 64import android.widget.ArrayAdapter; 65import android.widget.ImageView; 66import android.widget.ListView; 67import android.widget.RelativeLayout; 68import android.widget.Scroller; 69import android.widget.Toast; 70import android.widget.ZoomControls; 71import android.widget.AdapterView.OnItemClickListener; 72 73import java.io.File; 74import java.io.FileInputStream; 75import java.io.FileNotFoundException; 76import java.io.FileOutputStream; 77import java.io.IOException; 78import java.net.URLDecoder; 79import java.util.ArrayList; 80import java.util.HashMap; 81import java.util.List; 82 83/** 84 * <p>A View that displays web pages. This class is the basis upon 85 * which you can roll your own web browser or simply display some 86 * online content within your Activity. It uses the WebKit rendering 87 * engine to display web pages and includes methods to navigate 88 * forward and backward through a history, zoom in and out, perform 89 * text searches and more.</p> 90 * 91 * <p>Note that, in order for your Activity to access the Internet and 92 * load web pages in a WebView, you must add the <var>INTERNET</var> 93 * permissions to your Android Manifest file:</p> 94 * 95 * <pre><uses-permission android:name="android.permission.INTERNET" /></pre> 96 * 97 * <p>This must be a child of the <code><manifest></code> element.</p> 98 * 99 * <h3>Basic usage</h3> 100 * 101 * <p>By default, a WebView provides no browser-like widgets, does not 102 * enable JavaScript and errors will be ignored. If your goal is only 103 * to display some HTML as a part of your UI, this is probably fine; 104 * the user won't need to interact with the web page beyond reading 105 * it, and the web page won't need to interact with the user. If you 106 * actually want a fully blown web browser, then you probably want to 107 * invoke the Browser application with your URL rather than show it 108 * with a WebView. See {@link android.content.Intent} for more information.</p> 109 * 110 * <pre class="prettyprint"> 111 * WebView webview = new WebView(this); 112 * setContentView(webview); 113 * 114 * // Simplest usage: note that an exception will NOT be thrown 115 * // if there is an error loading this page (see below). 116 * webview.loadUrl("http://slashdot.org/"); 117 * 118 * // Of course you can also load from any string: 119 * String summary = "<html><body>You scored <b>192</b> points.</body></html>"; 120 * webview.loadData(summary, "text/html", "utf-8"); 121 * // ... although note that there are restrictions on what this HTML can do. 122 * // See the JavaDocs for loadData and loadDataWithBaseUrl for more info. 123 * </pre> 124 * 125 * <p>A WebView has several customization points where you can add your 126 * own behavior. These are:</p> 127 * 128 * <ul> 129 * <li>Creating and setting a {@link android.webkit.WebChromeClient} subclass. 130 * This class is called when something that might impact a 131 * browser UI happens, for instance, progress updates and 132 * JavaScript alerts are sent here. 133 * </li> 134 * <li>Creating and setting a {@link android.webkit.WebViewClient} subclass. 135 * It will be called when things happen that impact the 136 * rendering of the content, eg, errors or form submissions. You 137 * can also intercept URL loading here.</li> 138 * <li>Via the {@link android.webkit.WebSettings} class, which contains 139 * miscellaneous configuration. </li> 140 * <li>With the {@link android.webkit.WebView#addJavascriptInterface} method. 141 * This lets you bind Java objects into the WebView so they can be 142 * controlled from the web pages JavaScript.</li> 143 * </ul> 144 * 145 * <p>Here's a more complicated example, showing error handling, 146 * settings, and progress notification:</p> 147 * 148 * <pre class="prettyprint"> 149 * // Let's display the progress in the activity title bar, like the 150 * // browser app does. 151 * getWindow().requestFeature(Window.FEATURE_PROGRESS); 152 * 153 * webview.getSettings().setJavaScriptEnabled(true); 154 * 155 * final Activity activity = this; 156 * webview.setWebChromeClient(new WebChromeClient() { 157 * public void onProgressChanged(WebView view, int progress) { 158 * // Activities and WebViews measure progress with different scales. 159 * // The progress meter will automatically disappear when we reach 100% 160 * activity.setProgress(progress * 1000); 161 * } 162 * }); 163 * webview.setWebViewClient(new WebViewClient() { 164 * public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { 165 * Toast.makeText(activity, "Oh no! " + description, Toast.LENGTH_SHORT).show(); 166 * } 167 * }); 168 * 169 * webview.loadUrl("http://slashdot.org/"); 170 * </pre> 171 * 172 * <h3>Cookie and window management</h3> 173 * 174 * <p>For obvious security reasons, your application has its own 175 * cache, cookie store etc - it does not share the Browser 176 * applications data. Cookies are managed on a separate thread, so 177 * operations like index building don't block the UI 178 * thread. Follow the instructions in {@link android.webkit.CookieSyncManager} 179 * if you want to use cookies in your application. 180 * </p> 181 * 182 * <p>By default, requests by the HTML to open new windows are 183 * ignored. This is true whether they be opened by JavaScript or by 184 * the target attribute on a link. You can customize your 185 * WebChromeClient to provide your own behaviour for opening multiple windows, 186 * and render them in whatever manner you want.</p> 187 * 188 * <p>Standard behavior for an Activity is to be destroyed and 189 * recreated when the devices orientation is changed. This will cause 190 * the WebView to reload the current page. If you don't want that, you 191 * can set your Activity to handle the orientation and keyboardHidden 192 * changes, and then just leave the WebView alone. It'll automatically 193 * re-orient itself as appropriate.</p> 194 */ 195public class WebView extends AbsoluteLayout 196 implements ViewTreeObserver.OnGlobalFocusChangeListener, 197 ViewGroup.OnHierarchyChangeListener { 198 199 // if AUTO_REDRAW_HACK is true, then the CALL key will toggle redrawing 200 // the screen all-the-time. Good for profiling our drawing code 201 static private final boolean AUTO_REDRAW_HACK = false; 202 // true means redraw the screen all-the-time. Only with AUTO_REDRAW_HACK 203 private boolean mAutoRedraw; 204 205 // keep debugging parameters near the top of the file 206 static final String LOGTAG = "webview"; 207 static final boolean DEBUG = false; 208 static final boolean LOGV_ENABLED = DEBUG ? Config.LOGD : Config.LOGV; 209 210 private class ExtendedZoomControls extends RelativeLayout { 211 public ExtendedZoomControls(Context context, AttributeSet attrs) { 212 super(context, attrs); 213 LayoutInflater inflater = (LayoutInflater) context 214 .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 215 inflater.inflate(com.android.internal.R.layout.zoom_magnify, this 216 , true); 217 mZoomControls = (ZoomControls) findViewById 218 (com.android.internal.R.id.zoomControls); 219 mZoomMagnify = (ImageView) findViewById 220 (com.android.internal.R.id.zoomMagnify); 221 } 222 223 public void show(boolean showZoom, boolean canZoomOut) { 224 mZoomControls.setVisibility(showZoom ? View.VISIBLE : View.GONE); 225 mZoomMagnify.setVisibility(canZoomOut ? View.VISIBLE : View.GONE); 226 fade(View.VISIBLE, 0.0f, 1.0f); 227 } 228 229 public void hide() { 230 fade(View.GONE, 1.0f, 0.0f); 231 } 232 233 private void fade(int visibility, float startAlpha, float endAlpha) { 234 AlphaAnimation anim = new AlphaAnimation(startAlpha, endAlpha); 235 anim.setDuration(500); 236 startAnimation(anim); 237 setVisibility(visibility); 238 } 239 240 public void setIsZoomMagnifyEnabled(boolean isEnabled) { 241 mZoomMagnify.setEnabled(isEnabled); 242 } 243 244 public boolean hasFocus() { 245 return mZoomControls.hasFocus() || mZoomMagnify.hasFocus(); 246 } 247 248 public void setOnZoomInClickListener(OnClickListener listener) { 249 mZoomControls.setOnZoomInClickListener(listener); 250 } 251 252 public void setOnZoomOutClickListener(OnClickListener listener) { 253 mZoomControls.setOnZoomOutClickListener(listener); 254 } 255 256 public void setOnZoomMagnifyClickListener(OnClickListener listener) { 257 mZoomMagnify.setOnClickListener(listener); 258 } 259 260 ZoomControls mZoomControls; 261 ImageView mZoomMagnify; 262 } 263 264 /** 265 * Transportation object for returning WebView across thread boundaries. 266 */ 267 public class WebViewTransport { 268 private WebView mWebview; 269 270 /** 271 * Set the WebView to the transportation object. 272 * @param webview The WebView to transport. 273 */ 274 public synchronized void setWebView(WebView webview) { 275 mWebview = webview; 276 } 277 278 /** 279 * Return the WebView object. 280 * @return WebView The transported WebView object. 281 */ 282 public synchronized WebView getWebView() { 283 return mWebview; 284 } 285 } 286 287 // A final CallbackProxy shared by WebViewCore and BrowserFrame. 288 private final CallbackProxy mCallbackProxy; 289 290 private final WebViewDatabase mDatabase; 291 292 // SSL certificate for the main top-level page (if secure) 293 private SslCertificate mCertificate; 294 295 // Native WebView pointer that is 0 until the native object has been 296 // created. 297 private int mNativeClass; 298 // This would be final but it needs to be set to null when the WebView is 299 // destroyed. 300 private WebViewCore mWebViewCore; 301 // Handler for dispatching UI messages. 302 /* package */ final Handler mPrivateHandler = new PrivateHandler(); 303 private TextDialog mTextEntry; 304 // Used to ignore changes to webkit text that arrives to the UI side after 305 // more key events. 306 private int mTextGeneration; 307 308 // The list of loaded plugins. 309 private static PluginList sPluginList; 310 311 /** 312 * Position of the last touch event. 313 */ 314 private float mLastTouchX; 315 private float mLastTouchY; 316 317 /** 318 * Time of the last touch event. 319 */ 320 private long mLastTouchTime; 321 322 /** 323 * Time of the last time sending touch event to WebViewCore 324 */ 325 private long mLastSentTouchTime; 326 327 /** 328 * The minimum elapsed time before sending another ACTION_MOVE event to 329 * WebViewCore 330 */ 331 private static final int TOUCH_SENT_INTERVAL = 100; 332 333 /** 334 * Helper class to get velocity for fling 335 */ 336 VelocityTracker mVelocityTracker; 337 338 /** 339 * Touch mode 340 */ 341 private int mTouchMode = TOUCH_DONE_MODE; 342 private static final int TOUCH_INIT_MODE = 1; 343 private static final int TOUCH_DRAG_START_MODE = 2; 344 private static final int TOUCH_DRAG_MODE = 3; 345 private static final int TOUCH_SHORTPRESS_START_MODE = 4; 346 private static final int TOUCH_SHORTPRESS_MODE = 5; 347 private static final int TOUCH_DOUBLECLICK_MODE = 6; 348 private static final int TOUCH_DONE_MODE = 7; 349 private static final int TOUCH_SELECT_MODE = 8; 350 // touch mode values specific to scale+scroll 351 private static final int FIRST_SCROLL_ZOOM = 9; 352 private static final int SCROLL_ZOOM_ANIMATION_IN = 9; 353 private static final int SCROLL_ZOOM_ANIMATION_OUT = 10; 354 private static final int SCROLL_ZOOM_OUT = 11; 355 private static final int LAST_SCROLL_ZOOM = 11; 356 // end of touch mode values specific to scale+scroll 357 358 // Whether to forward the touch events to WebCore 359 private boolean mForwardTouchEvents = false; 360 361 // If updateTextEntry gets called while we are out of focus, use this 362 // variable to remember to do it next time we gain focus. 363 private boolean mNeedsUpdateTextEntry = false; 364 365 // Whether or not to draw the focus ring. 366 private boolean mDrawFocusRing = true; 367 368 /** 369 * Customizable constant 370 */ 371 // pre-computed square of ViewConfiguration.getTouchSlop() 372 private static final int TOUCH_SLOP_SQUARE = 373 ViewConfiguration.getTouchSlop() * ViewConfiguration.getTouchSlop(); 374 // This should be ViewConfiguration.getTapTimeout() 375 // But system time out is 100ms, which is too short for the browser. 376 // In the browser, if it switches out of tap too soon, jump tap won't work. 377 private static final int TAP_TIMEOUT = 200; 378 // The duration in milliseconds we will wait to see if it is a double tap. 379 // With a limited survey, the time between the first tap up and the second 380 // tap down in the double tap case is around 70ms - 120ms. 381 private static final int DOUBLE_TAP_TIMEOUT = 200; 382 // This should be ViewConfiguration.getLongPressTimeout() 383 // But system time out is 500ms, which is too short for the browser. 384 // With a short timeout, it's difficult to treat trigger a short press. 385 private static final int LONG_PRESS_TIMEOUT = 1000; 386 // needed to avoid flinging after a pause of no movement 387 private static final int MIN_FLING_TIME = 250; 388 // The time that the Zoom Controls are visible before fading away 389 private static final long ZOOM_CONTROLS_TIMEOUT = 390 ViewConfiguration.getZoomControlsTimeout(); 391 // The amount of content to overlap between two screens when going through 392 // pages with the space bar, in pixels. 393 private static final int PAGE_SCROLL_OVERLAP = 24; 394 395 /** 396 * These prevent calling requestLayout if either dimension is fixed. This 397 * depends on the layout parameters and the measure specs. 398 */ 399 boolean mWidthCanMeasure; 400 boolean mHeightCanMeasure; 401 402 // Remember the last dimensions we sent to the native side so we can avoid 403 // sending the same dimensions more than once. 404 int mLastWidthSent; 405 int mLastHeightSent; 406 407 private int mContentWidth; // cache of value from WebViewCore 408 private int mContentHeight; // cache of value from WebViewCore 409 410 // Need to have the separate control for horizontal and vertical scrollbar 411 // style than the View's single scrollbar style 412 private boolean mOverlayHorizontalScrollbar = true; 413 private boolean mOverlayVerticalScrollbar = false; 414 415 // our standard speed. this way small distances will be traversed in less 416 // time than large distances, but we cap the duration, so that very large 417 // distances won't take too long to get there. 418 private static final int STD_SPEED = 480; // pixels per second 419 // time for the longest scroll animation 420 private static final int MAX_DURATION = 750; // milliseconds 421 private Scroller mScroller; 422 423 private boolean mWrapContent; 424 425 // The View containing the zoom controls 426 private ExtendedZoomControls mZoomControls; 427 private Runnable mZoomControlRunnable; 428 429 // true if we should call webcore to draw the content, false means we have 430 // requested something but it isn't ready to draw yet. 431 private WebViewCore.FocusData mFocusData; 432 /** 433 * Private message ids 434 */ 435 private static final int REMEMBER_PASSWORD = 1; 436 private static final int NEVER_REMEMBER_PASSWORD = 2; 437 private static final int SWITCH_TO_SHORTPRESS = 3; 438 private static final int SWITCH_TO_LONGPRESS = 4; 439 private static final int RELEASE_SINGLE_TAP = 5; 440 private static final int UPDATE_TEXT_ENTRY_ADAPTER = 6; 441 private static final int SWITCH_TO_ENTER = 7; 442 private static final int RESUME_WEBCORE_UPDATE = 8; 443 444 //! arg1=x, arg2=y 445 static final int SCROLL_TO_MSG_ID = 10; 446 static final int SCROLL_BY_MSG_ID = 11; 447 //! arg1=x, arg2=y 448 static final int SPAWN_SCROLL_TO_MSG_ID = 12; 449 //! arg1=x, arg2=y 450 static final int SYNC_SCROLL_TO_MSG_ID = 13; 451 static final int NEW_PICTURE_MSG_ID = 14; 452 static final int UPDATE_TEXT_ENTRY_MSG_ID = 15; 453 static final int WEBCORE_INITIALIZED_MSG_ID = 16; 454 static final int UPDATE_TEXTFIELD_TEXT_MSG_ID = 17; 455 static final int DID_FIRST_LAYOUT_MSG_ID = 18; 456 static final int RECOMPUTE_FOCUS_MSG_ID = 19; 457 static final int NOTIFY_FOCUS_SET_MSG_ID = 20; 458 static final int MARK_NODE_INVALID_ID = 21; 459 static final int UPDATE_CLIPBOARD = 22; 460 static final int LONG_PRESS_ENTER = 23; 461 static final int PREVENT_TOUCH_ID = 24; 462 static final int WEBCORE_NEED_TOUCH_EVENTS = 25; 463 464 // width which view is considered to be fully zoomed out 465 static final int ZOOM_OUT_WIDTH = 1024; 466 467 private static final float DEFAULT_MAX_ZOOM_SCALE = 4; 468 private static final float DEFAULT_MIN_ZOOM_SCALE = 0.25f; 469 // scale limit, which can be set through viewport meta tag in the web page 470 private float mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE; 471 private float mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE; 472 473 // initial scale in percent. 0 means using default. 474 private int mInitialScale = 0; 475 476 // computed scale and inverse, from mZoomWidth. 477 private float mActualScale = 1; 478 private float mInvActualScale = 1; 479 // if this is non-zero, it is used on drawing rather than mActualScale 480 private float mZoomScale; 481 private float mInvInitialZoomScale; 482 private float mInvFinalZoomScale; 483 private long mZoomStart; 484 private static final int ZOOM_ANIMATION_LENGTH = 500; 485 486 private boolean mUserScroll = false; 487 488 private int mSnapScrollMode = SNAP_NONE; 489 private static final int SNAP_NONE = 1; 490 private static final int SNAP_X = 2; 491 private static final int SNAP_Y = 3; 492 private static final int SNAP_X_LOCK = 4; 493 private static final int SNAP_Y_LOCK = 5; 494 private boolean mSnapPositive; 495 496 // Used to match key downs and key ups 497 private boolean mGotKeyDown; 498 499 /** 500 * URI scheme for telephone number 501 */ 502 public static final String SCHEME_TEL = "tel:"; 503 /** 504 * URI scheme for email address 505 */ 506 public static final String SCHEME_MAILTO = "mailto:"; 507 /** 508 * URI scheme for map address 509 */ 510 public static final String SCHEME_GEO = "geo:0,0?q="; 511 512 private int mBackgroundColor = Color.WHITE; 513 514 // Used to notify listeners of a new picture. 515 private PictureListener mPictureListener; 516 /** 517 * Interface to listen for new pictures as they change. 518 */ 519 public interface PictureListener { 520 /** 521 * Notify the listener that the picture has changed. 522 * @param view The WebView that owns the picture. 523 * @param picture The new picture. 524 */ 525 public void onNewPicture(WebView view, Picture picture); 526 } 527 528 public class HitTestResult { 529 /** 530 * Default HitTestResult, where the target is unknown 531 */ 532 public static final int UNKNOWN_TYPE = 0; 533 /** 534 * HitTestResult for hitting a HTML::a tag 535 */ 536 public static final int ANCHOR_TYPE = 1; 537 /** 538 * HitTestResult for hitting a phone number 539 */ 540 public static final int PHONE_TYPE = 2; 541 /** 542 * HitTestResult for hitting a map address 543 */ 544 public static final int GEO_TYPE = 3; 545 /** 546 * HitTestResult for hitting an email address 547 */ 548 public static final int EMAIL_TYPE = 4; 549 /** 550 * HitTestResult for hitting an HTML::img tag 551 */ 552 public static final int IMAGE_TYPE = 5; 553 /** 554 * HitTestResult for hitting a HTML::a tag which contains HTML::img 555 */ 556 public static final int IMAGE_ANCHOR_TYPE = 6; 557 /** 558 * HitTestResult for hitting a HTML::a tag with src=http 559 */ 560 public static final int SRC_ANCHOR_TYPE = 7; 561 /** 562 * HitTestResult for hitting a HTML::a tag with src=http + HTML::img 563 */ 564 public static final int SRC_IMAGE_ANCHOR_TYPE = 8; 565 /** 566 * HitTestResult for hitting an edit text area 567 */ 568 public static final int EDIT_TEXT_TYPE = 9; 569 570 private int mType; 571 private String mExtra; 572 573 HitTestResult() { 574 mType = UNKNOWN_TYPE; 575 } 576 577 private void setType(int type) { 578 mType = type; 579 } 580 581 private void setExtra(String extra) { 582 mExtra = extra; 583 } 584 585 public int getType() { 586 return mType; 587 } 588 589 public String getExtra() { 590 return mExtra; 591 } 592 } 593 594 /** 595 * Construct a new WebView with a Context object. 596 * @param context A Context object used to access application assets. 597 */ 598 public WebView(Context context) { 599 this(context, null); 600 } 601 602 /** 603 * Construct a new WebView with layout parameters. 604 * @param context A Context object used to access application assets. 605 * @param attrs An AttributeSet passed to our parent. 606 */ 607 public WebView(Context context, AttributeSet attrs) { 608 this(context, attrs, com.android.internal.R.attr.webViewStyle); 609 } 610 611 /** 612 * Construct a new WebView with layout parameters and a default style. 613 * @param context A Context object used to access application assets. 614 * @param attrs An AttributeSet passed to our parent. 615 * @param defStyle The default style resource ID. 616 */ 617 public WebView(Context context, AttributeSet attrs, int defStyle) { 618 super(context, attrs, defStyle); 619 init(); 620 621 TypedArray a = context.obtainStyledAttributes( 622 com.android.internal.R.styleable.View); 623 initializeScrollbars(a); 624 a.recycle(); 625 626 mCallbackProxy = new CallbackProxy(context, this); 627 mWebViewCore = new WebViewCore(context, this, mCallbackProxy); 628 mDatabase = WebViewDatabase.getInstance(context); 629 mFocusData = new WebViewCore.FocusData(); 630 mFocusData.mFrame = 0; 631 mFocusData.mNode = 0; 632 mFocusData.mX = 0; 633 mFocusData.mY = 0; 634 mScroller = new Scroller(context); 635 } 636 637 private void init() { 638 setWillNotDraw(false); 639 setFocusable(true); 640 setFocusableInTouchMode(true); 641 setClickable(true); 642 setLongClickable(true); 643 644 // should be conditional on if we're in the browser activity? 645 setHorizontalScrollBarEnabled(true); 646 setVerticalScrollBarEnabled(true); 647 } 648 649 /* package */ boolean onSavePassword(String schemePlusHost, String username, 650 String password, final Message resumeMsg) { 651 boolean rVal = false; 652 if (resumeMsg == null) { 653 // null resumeMsg implies saving password silently 654 mDatabase.setUsernamePassword(schemePlusHost, username, password); 655 } else { 656 final Message remember = mPrivateHandler.obtainMessage( 657 REMEMBER_PASSWORD); 658 remember.getData().putString("host", schemePlusHost); 659 remember.getData().putString("username", username); 660 remember.getData().putString("password", password); 661 remember.obj = resumeMsg; 662 663 final Message neverRemember = mPrivateHandler.obtainMessage( 664 NEVER_REMEMBER_PASSWORD); 665 neverRemember.getData().putString("host", schemePlusHost); 666 neverRemember.getData().putString("username", username); 667 neverRemember.getData().putString("password", password); 668 neverRemember.obj = resumeMsg; 669 670 new AlertDialog.Builder(getContext()) 671 .setTitle(com.android.internal.R.string.save_password_label) 672 .setMessage(com.android.internal.R.string.save_password_message) 673 .setPositiveButton(com.android.internal.R.string.save_password_notnow, 674 new DialogInterface.OnClickListener() { 675 public void onClick(DialogInterface dialog, int which) { 676 resumeMsg.sendToTarget(); 677 } 678 }) 679 .setNeutralButton(com.android.internal.R.string.save_password_remember, 680 new DialogInterface.OnClickListener() { 681 public void onClick(DialogInterface dialog, int which) { 682 remember.sendToTarget(); 683 } 684 }) 685 .setNegativeButton(com.android.internal.R.string.save_password_never, 686 new DialogInterface.OnClickListener() { 687 public void onClick(DialogInterface dialog, int which) { 688 neverRemember.sendToTarget(); 689 } 690 }) 691 .setOnCancelListener(new OnCancelListener() { 692 public void onCancel(DialogInterface dialog) { 693 resumeMsg.sendToTarget(); 694 } 695 }).show(); 696 // Return true so that WebViewCore will pause while the dialog is 697 // up. 698 rVal = true; 699 } 700 return rVal; 701 } 702 703 @Override 704 public void setScrollBarStyle(int style) { 705 if (style == View.SCROLLBARS_INSIDE_INSET 706 || style == View.SCROLLBARS_OUTSIDE_INSET) { 707 mOverlayHorizontalScrollbar = mOverlayVerticalScrollbar = false; 708 } else { 709 mOverlayHorizontalScrollbar = mOverlayVerticalScrollbar = true; 710 } 711 super.setScrollBarStyle(style); 712 } 713 714 /** 715 * Specify whether the horizontal scrollbar has overlay style. 716 * @param overlay TRUE if horizontal scrollbar should have overlay style. 717 */ 718 public void setHorizontalScrollbarOverlay(boolean overlay) { 719 mOverlayHorizontalScrollbar = overlay; 720 } 721 722 /** 723 * Specify whether the vertical scrollbar has overlay style. 724 * @param overlay TRUE if vertical scrollbar should have overlay style. 725 */ 726 public void setVerticalScrollbarOverlay(boolean overlay) { 727 mOverlayVerticalScrollbar = overlay; 728 } 729 730 /** 731 * Return whether horizontal scrollbar has overlay style 732 * @return TRUE if horizontal scrollbar has overlay style. 733 */ 734 public boolean overlayHorizontalScrollbar() { 735 return mOverlayHorizontalScrollbar; 736 } 737 738 /** 739 * Return whether vertical scrollbar has overlay style 740 * @return TRUE if vertical scrollbar has overlay style. 741 */ 742 public boolean overlayVerticalScrollbar() { 743 return mOverlayVerticalScrollbar; 744 } 745 746 /* 747 * Return the width of the view where the content of WebView should render 748 * to. 749 */ 750 private int getViewWidth() { 751 if (mOverlayVerticalScrollbar) { 752 return getWidth(); 753 } else { 754 return getWidth() - getVerticalScrollbarWidth(); 755 } 756 } 757 758 /* 759 * Return the height of the view where the content of WebView should render 760 * to. 761 */ 762 private int getViewHeight() { 763 if (mOverlayHorizontalScrollbar) { 764 return getHeight(); 765 } else { 766 return getHeight() - getHorizontalScrollbarHeight(); 767 } 768 } 769 770 /** 771 * @return The SSL certificate for the main top-level page or null if 772 * there is no certificate (the site is not secure). 773 */ 774 public SslCertificate getCertificate() { 775 return mCertificate; 776 } 777 778 /** 779 * Sets the SSL certificate for the main top-level page. 780 */ 781 public void setCertificate(SslCertificate certificate) { 782 // here, the certificate can be null (if the site is not secure) 783 mCertificate = certificate; 784 } 785 786 //------------------------------------------------------------------------- 787 // Methods called by activity 788 //------------------------------------------------------------------------- 789 790 /** 791 * Save the username and password for a particular host in the WebView's 792 * internal database. 793 * @param host The host that required the credentials. 794 * @param username The username for the given host. 795 * @param password The password for the given host. 796 */ 797 public void savePassword(String host, String username, String password) { 798 mDatabase.setUsernamePassword(host, username, password); 799 } 800 801 /** 802 * Set the HTTP authentication credentials for a given host and realm. 803 * 804 * @param host The host for the credentials. 805 * @param realm The realm for the credentials. 806 * @param username The username for the password. If it is null, it means 807 * password can't be saved. 808 * @param password The password 809 */ 810 public void setHttpAuthUsernamePassword(String host, String realm, 811 String username, String password) { 812 mDatabase.setHttpAuthUsernamePassword(host, realm, username, password); 813 } 814 815 /** 816 * Retrieve the HTTP authentication username and password for a given 817 * host & realm pair 818 * 819 * @param host The host for which the credentials apply. 820 * @param realm The realm for which the credentials apply. 821 * @return String[] if found, String[0] is username, which can be null and 822 * String[1] is password. Return null if it can't find anything. 823 */ 824 public String[] getHttpAuthUsernamePassword(String host, String realm) { 825 return mDatabase.getHttpAuthUsernamePassword(host, realm); 826 } 827 828 /** 829 * Destroy the internal state of the WebView. This method should be called 830 * after the WebView has been removed from the view system. No other 831 * methods may be called on a WebView after destroy. 832 */ 833 public void destroy() { 834 clearTextEntry(); 835 getViewTreeObserver().removeOnGlobalFocusChangeListener(this); 836 if (mWebViewCore != null) { 837 // Set the handlers to null before destroying WebViewCore so no 838 // more messages will be posted. 839 mCallbackProxy.setWebViewClient(null); 840 mCallbackProxy.setWebChromeClient(null); 841 // Tell WebViewCore to destroy itself 842 WebViewCore webViewCore = mWebViewCore; 843 mWebViewCore = null; // prevent using partial webViewCore 844 webViewCore.destroy(); 845 // Remove any pending messages that might not be serviced yet. 846 mPrivateHandler.removeCallbacksAndMessages(null); 847 mCallbackProxy.removeCallbacksAndMessages(null); 848 // Wake up the WebCore thread just in case it is waiting for a 849 // javascript dialog. 850 synchronized (mCallbackProxy) { 851 mCallbackProxy.notify(); 852 } 853 } 854 if (mNativeClass != 0) { 855 nativeDestroy(); 856 mNativeClass = 0; 857 } 858 } 859 860 /** 861 * Enables platform notifications of data state and proxy changes. 862 */ 863 public static void enablePlatformNotifications() { 864 Network.enablePlatformNotifications(); 865 } 866 867 /** 868 * If platform notifications are enabled, this should be called 869 * from onPause() or onStop(). 870 */ 871 public static void disablePlatformNotifications() { 872 Network.disablePlatformNotifications(); 873 } 874 875 /** 876 * Inform WebView of the network state. This is used to set 877 * the javascript property window.navigator.isOnline and 878 * generates the online/offline event as specified in HTML5, sec. 5.7.7 879 * @param networkUp boolean indicating if network is available 880 * 881 * @hide pending API Council approval 882 */ 883 public void setNetworkAvailable(boolean networkUp) { 884 BrowserFrame.sJavaBridge.setNetworkOnLine(networkUp); 885 } 886 887 /** 888 * Save the state of this WebView used in 889 * {@link android.app.Activity#onSaveInstanceState}. Please note that this 890 * method no longer stores the display data for this WebView. The previous 891 * behavior could potentially leak files if {@link #restoreState} was never 892 * called. See {@link #savePicture} and {@link #restorePicture} for saving 893 * and restoring the display data. 894 * @param outState The Bundle to store the WebView state. 895 * @return The same copy of the back/forward list used to save the state. If 896 * saveState fails, the returned list will be null. 897 * @see #savePicture 898 * @see #restorePicture 899 */ 900 public WebBackForwardList saveState(Bundle outState) { 901 if (outState == null) { 902 return null; 903 } 904 // We grab a copy of the back/forward list because a client of WebView 905 // may have invalidated the history list by calling clearHistory. 906 WebBackForwardList list = copyBackForwardList(); 907 final int currentIndex = list.getCurrentIndex(); 908 final int size = list.getSize(); 909 // We should fail saving the state if the list is empty or the index is 910 // not in a valid range. 911 if (currentIndex < 0 || currentIndex >= size || size == 0) { 912 return null; 913 } 914 outState.putInt("index", currentIndex); 915 // FIXME: This should just be a byte[][] instead of ArrayList but 916 // Parcel.java does not have the code to handle multi-dimensional 917 // arrays. 918 ArrayList<byte[]> history = new ArrayList<byte[]>(size); 919 for (int i = 0; i < size; i++) { 920 WebHistoryItem item = list.getItemAtIndex(i); 921 byte[] data = item.getFlattenedData(); 922 if (data == null) { 923 // It would be very odd to not have any data for a given history 924 // item. And we will fail to rebuild the history list without 925 // flattened data. 926 return null; 927 } 928 history.add(data); 929 } 930 outState.putSerializable("history", history); 931 if (mCertificate != null) { 932 outState.putBundle("certificate", 933 SslCertificate.saveState(mCertificate)); 934 } 935 return list; 936 } 937 938 /** 939 * Save the current display data to the Bundle given. Used in conjunction 940 * with {@link #saveState}. 941 * @param b A Bundle to store the display data. 942 * @param dest The file to store the serialized picture data. Will be 943 * overwritten with this WebView's picture data. 944 * @return True if the picture was successfully saved. 945 */ 946 public boolean savePicture(Bundle b, File dest) { 947 if (dest == null || b == null) { 948 return false; 949 } 950 final Picture p = capturePicture(); 951 try { 952 final FileOutputStream out = new FileOutputStream(dest); 953 p.writeToStream(out); 954 out.close(); 955 } catch (FileNotFoundException e){ 956 e.printStackTrace(); 957 } catch (IOException e) { 958 e.printStackTrace(); 959 } catch (RuntimeException e) { 960 e.printStackTrace(); 961 } 962 if (dest.length() > 0) { 963 b.putInt("scrollX", mScrollX); 964 b.putInt("scrollY", mScrollY); 965 b.putFloat("scale", mActualScale); 966 return true; 967 } 968 return false; 969 } 970 971 /** 972 * Restore the display data that was save in {@link #savePicture}. Used in 973 * conjunction with {@link #restoreState}. 974 * @param b A Bundle containing the saved display data. 975 * @param src The file where the picture data was stored. 976 * @return True if the picture was successfully restored. 977 */ 978 public boolean restorePicture(Bundle b, File src) { 979 if (src == null || b == null) { 980 return false; 981 } 982 if (src.exists()) { 983 Picture p = null; 984 try { 985 final FileInputStream in = new FileInputStream(src); 986 p = Picture.createFromStream(in); 987 in.close(); 988 } catch (FileNotFoundException e){ 989 e.printStackTrace(); 990 } catch (RuntimeException e) { 991 e.printStackTrace(); 992 } catch (IOException e) { 993 e.printStackTrace(); 994 } 995 if (p != null) { 996 int sx = b.getInt("scrollX", 0); 997 int sy = b.getInt("scrollY", 0); 998 float scale = b.getFloat("scale", 1.0f); 999 mDrawHistory = true; 1000 mHistoryPicture = p; 1001 mScrollX = sx; 1002 mScrollY = sy; 1003 mHistoryWidth = Math.round(p.getWidth() * scale); 1004 mHistoryHeight = Math.round(p.getHeight() * scale); 1005 // as getWidth() / getHeight() of the view are not 1006 // available yet, set up mActualScale, so that when 1007 // onSizeChanged() is called, the rest will be set 1008 // correctly 1009 mActualScale = scale; 1010 invalidate(); 1011 return true; 1012 } 1013 } 1014 return false; 1015 } 1016 1017 /** 1018 * Restore the state of this WebView from the given map used in 1019 * {@link android.app.Activity#onRestoreInstanceState}. This method should 1020 * be called to restore the state of the WebView before using the object. If 1021 * it is called after the WebView has had a chance to build state (load 1022 * pages, create a back/forward list, etc.) there may be undesirable 1023 * side-effects. Please note that this method no longer restores the 1024 * display data for this WebView. See {@link #savePicture} and {@link 1025 * #restorePicture} for saving and restoring the display data. 1026 * @param inState The incoming Bundle of state. 1027 * @return The restored back/forward list or null if restoreState failed. 1028 * @see #savePicture 1029 * @see #restorePicture 1030 */ 1031 public WebBackForwardList restoreState(Bundle inState) { 1032 WebBackForwardList returnList = null; 1033 if (inState == null) { 1034 return returnList; 1035 } 1036 if (inState.containsKey("index") && inState.containsKey("history")) { 1037 mCertificate = SslCertificate.restoreState( 1038 inState.getBundle("certificate")); 1039 1040 final WebBackForwardList list = mCallbackProxy.getBackForwardList(); 1041 final int index = inState.getInt("index"); 1042 // We can't use a clone of the list because we need to modify the 1043 // shared copy, so synchronize instead to prevent concurrent 1044 // modifications. 1045 synchronized (list) { 1046 final List<byte[]> history = 1047 (List<byte[]>) inState.getSerializable("history"); 1048 final int size = history.size(); 1049 // Check the index bounds so we don't crash in native code while 1050 // restoring the history index. 1051 if (index < 0 || index >= size) { 1052 return null; 1053 } 1054 for (int i = 0; i < size; i++) { 1055 byte[] data = history.remove(0); 1056 if (data == null) { 1057 // If we somehow have null data, we cannot reconstruct 1058 // the item and thus our history list cannot be rebuilt. 1059 return null; 1060 } 1061 WebHistoryItem item = new WebHistoryItem(data); 1062 list.addHistoryItem(item); 1063 } 1064 // Grab the most recent copy to return to the caller. 1065 returnList = copyBackForwardList(); 1066 // Update the copy to have the correct index. 1067 returnList.setCurrentIndex(index); 1068 } 1069 // Remove all pending messages because we are restoring previous 1070 // state. 1071 mWebViewCore.removeMessages(); 1072 // Send a restore state message. 1073 mWebViewCore.sendMessage(EventHub.RESTORE_STATE, index); 1074 } 1075 return returnList; 1076 } 1077 1078 /** 1079 * Load the given url. 1080 * @param url The url of the resource to load. 1081 */ 1082 public void loadUrl(String url) { 1083 switchOutDrawHistory(); 1084 mWebViewCore.sendMessage(EventHub.LOAD_URL, url); 1085 clearTextEntry(); 1086 } 1087 1088 /** 1089 * Load the given data into the WebView. This will load the data into 1090 * WebView using the data: scheme. Content loaded through this mechanism 1091 * does not have the ability to load content from the network. 1092 * @param data A String of data in the given encoding. 1093 * @param mimeType The MIMEType of the data. i.e. text/html, image/jpeg 1094 * @param encoding The encoding of the data. i.e. utf-8, base64 1095 */ 1096 public void loadData(String data, String mimeType, String encoding) { 1097 loadUrl("data:" + mimeType + ";" + encoding + "," + data); 1098 } 1099 1100 /** 1101 * Load the given data into the WebView, use the provided URL as the base 1102 * URL for the content. The base URL is the URL that represents the page 1103 * that is loaded through this interface. As such, it is used for the 1104 * history entry and to resolve any relative URLs. The failUrl is used if 1105 * browser fails to load the data provided. If it is empty or null, and the 1106 * load fails, then no history entry is created. 1107 * <p> 1108 * Note for post 1.0. Due to the change in the WebKit, the access to asset 1109 * files through "file:///android_asset/" for the sub resources is more 1110 * restricted. If you provide null or empty string as baseUrl, you won't be 1111 * able to access asset files. If the baseUrl is anything other than 1112 * http(s)/ftp(s)/about/javascript as scheme, you can access asset files for 1113 * sub resources. 1114 * 1115 * @param baseUrl Url to resolve relative paths with, if null defaults to 1116 * "about:blank" 1117 * @param data A String of data in the given encoding. 1118 * @param mimeType The MIMEType of the data. i.e. text/html. If null, 1119 * defaults to "text/html" 1120 * @param encoding The encoding of the data. i.e. utf-8, us-ascii 1121 * @param failUrl URL to use if the content fails to load or null. 1122 */ 1123 public void loadDataWithBaseURL(String baseUrl, String data, 1124 String mimeType, String encoding, String failUrl) { 1125 1126 if (baseUrl != null && baseUrl.toLowerCase().startsWith("data:")) { 1127 loadData(data, mimeType, encoding); 1128 return; 1129 } 1130 switchOutDrawHistory(); 1131 HashMap arg = new HashMap(); 1132 arg.put("baseUrl", baseUrl); 1133 arg.put("data", data); 1134 arg.put("mimeType", mimeType); 1135 arg.put("encoding", encoding); 1136 arg.put("failUrl", failUrl); 1137 mWebViewCore.sendMessage(EventHub.LOAD_DATA, arg); 1138 clearTextEntry(); 1139 } 1140 1141 /** 1142 * Stop the current load. 1143 */ 1144 public void stopLoading() { 1145 // TODO: should we clear all the messages in the queue before sending 1146 // STOP_LOADING? 1147 switchOutDrawHistory(); 1148 mWebViewCore.sendMessage(EventHub.STOP_LOADING); 1149 } 1150 1151 /** 1152 * Reload the current url. 1153 */ 1154 public void reload() { 1155 switchOutDrawHistory(); 1156 mWebViewCore.sendMessage(EventHub.RELOAD); 1157 } 1158 1159 /** 1160 * Return true if this WebView has a back history item. 1161 * @return True iff this WebView has a back history item. 1162 */ 1163 public boolean canGoBack() { 1164 WebBackForwardList l = mCallbackProxy.getBackForwardList(); 1165 synchronized (l) { 1166 if (l.getClearPending()) { 1167 return false; 1168 } else { 1169 return l.getCurrentIndex() > 0; 1170 } 1171 } 1172 } 1173 1174 /** 1175 * Go back in the history of this WebView. 1176 */ 1177 public void goBack() { 1178 goBackOrForward(-1); 1179 } 1180 1181 /** 1182 * Return true if this WebView has a forward history item. 1183 * @return True iff this Webview has a forward history item. 1184 */ 1185 public boolean canGoForward() { 1186 WebBackForwardList l = mCallbackProxy.getBackForwardList(); 1187 synchronized (l) { 1188 if (l.getClearPending()) { 1189 return false; 1190 } else { 1191 return l.getCurrentIndex() < l.getSize() - 1; 1192 } 1193 } 1194 } 1195 1196 /** 1197 * Go forward in the history of this WebView. 1198 */ 1199 public void goForward() { 1200 goBackOrForward(1); 1201 } 1202 1203 /** 1204 * Return true if the page can go back or forward the given 1205 * number of steps. 1206 * @param steps The negative or positive number of steps to move the 1207 * history. 1208 */ 1209 public boolean canGoBackOrForward(int steps) { 1210 WebBackForwardList l = mCallbackProxy.getBackForwardList(); 1211 synchronized (l) { 1212 if (l.getClearPending()) { 1213 return false; 1214 } else { 1215 int newIndex = l.getCurrentIndex() + steps; 1216 return newIndex >= 0 && newIndex < l.getSize(); 1217 } 1218 } 1219 } 1220 1221 /** 1222 * Go to the history item that is the number of steps away from 1223 * the current item. Steps is negative if backward and positive 1224 * if forward. 1225 * @param steps The number of steps to take back or forward in the back 1226 * forward list. 1227 */ 1228 public void goBackOrForward(int steps) { 1229 goBackOrForward(steps, false); 1230 } 1231 1232 private void goBackOrForward(int steps, boolean ignoreSnapshot) { 1233 // every time we go back or forward, we want to reset the 1234 // WebView certificate: 1235 // if the new site is secure, we will reload it and get a 1236 // new certificate set; 1237 // if the new site is not secure, the certificate must be 1238 // null, and that will be the case 1239 mCertificate = null; 1240 if (steps != 0) { 1241 clearTextEntry(); 1242 mWebViewCore.sendMessage(EventHub.GO_BACK_FORWARD, steps, 1243 ignoreSnapshot ? 1 : 0); 1244 } 1245 } 1246 1247 private boolean extendScroll(int y) { 1248 int finalY = mScroller.getFinalY(); 1249 int newY = pinLocY(finalY + y); 1250 if (newY == finalY) return false; 1251 mScroller.setFinalY(newY); 1252 mScroller.extendDuration(computeDuration(0, y)); 1253 return true; 1254 } 1255 1256 /** 1257 * Scroll the contents of the view up by half the view size 1258 * @param top true to jump to the top of the page 1259 * @return true if the page was scrolled 1260 */ 1261 public boolean pageUp(boolean top) { 1262 if (mNativeClass == 0) { 1263 return false; 1264 } 1265 nativeClearFocus(-1, -1); 1266 if (top) { 1267 // go to the top of the document 1268 return pinScrollTo(mScrollX, 0, true); 1269 } 1270 // Page up 1271 int h = getHeight(); 1272 int y; 1273 if (h > 2 * PAGE_SCROLL_OVERLAP) { 1274 y = -h + PAGE_SCROLL_OVERLAP; 1275 } else { 1276 y = -h / 2; 1277 } 1278 mUserScroll = true; 1279 return mScroller.isFinished() ? pinScrollBy(0, y, true) 1280 : extendScroll(y); 1281 } 1282 1283 /** 1284 * Scroll the contents of the view down by half the page size 1285 * @param bottom true to jump to bottom of page 1286 * @return true if the page was scrolled 1287 */ 1288 public boolean pageDown(boolean bottom) { 1289 if (mNativeClass == 0) { 1290 return false; 1291 } 1292 nativeClearFocus(-1, -1); 1293 if (bottom) { 1294 return pinScrollTo(mScrollX, mContentHeight, true); 1295 } 1296 // Page down. 1297 int h = getHeight(); 1298 int y; 1299 if (h > 2 * PAGE_SCROLL_OVERLAP) { 1300 y = h - PAGE_SCROLL_OVERLAP; 1301 } else { 1302 y = h / 2; 1303 } 1304 mUserScroll = true; 1305 return mScroller.isFinished() ? pinScrollBy(0, y, true) 1306 : extendScroll(y); 1307 } 1308 1309 /** 1310 * Clear the view so that onDraw() will draw nothing but white background, 1311 * and onMeasure() will return 0 if MeasureSpec is not MeasureSpec.EXACTLY 1312 */ 1313 public void clearView() { 1314 mContentWidth = 0; 1315 mContentHeight = 0; 1316 mWebViewCore.sendMessage(EventHub.CLEAR_CONTENT); 1317 } 1318 1319 /** 1320 * Return a new picture that captures the current display of the webview. 1321 * This is a copy of the display, and will be unaffected if the webview 1322 * later loads a different URL. 1323 * 1324 * @return a picture containing the current contents of the view. Note this 1325 * picture is of the entire document, and is not restricted to the 1326 * bounds of the view. 1327 */ 1328 public Picture capturePicture() { 1329 if (null == mWebViewCore) return null; // check for out of memory tab 1330 return mWebViewCore.copyContentPicture(); 1331 } 1332 1333 /** 1334 * Return true if the browser is displaying a TextView for text input. 1335 */ 1336 private boolean inEditingMode() { 1337 return mTextEntry != null && mTextEntry.getParent() != null 1338 && mTextEntry.hasFocus(); 1339 } 1340 1341 private void clearTextEntry() { 1342 if (inEditingMode()) { 1343 mTextEntry.remove(); 1344 } 1345 } 1346 1347 /** 1348 * Return the current scale of the WebView 1349 * @return The current scale. 1350 */ 1351 public float getScale() { 1352 return mActualScale; 1353 } 1354 1355 /** 1356 * Set the initial scale for the WebView. 0 means default. If 1357 * {@link WebSettings#getUseWideViewPort()} is true, it zooms out all the 1358 * way. Otherwise it starts with 100%. If initial scale is greater than 0, 1359 * WebView starts will this value as initial scale. 1360 * 1361 * @param scaleInPercent The initial scale in percent. 1362 */ 1363 public void setInitialScale(int scaleInPercent) { 1364 mInitialScale = scaleInPercent; 1365 } 1366 1367 /** 1368 * Invoke the graphical zoom picker widget for this WebView. This will 1369 * result in the zoom widget appearing on the screen to control the zoom 1370 * level of this WebView. 1371 */ 1372 public void invokeZoomPicker() { 1373 if (!getSettings().supportZoom()) { 1374 Log.w(LOGTAG, "This WebView doesn't support zoom."); 1375 return; 1376 } 1377 clearTextEntry(); 1378 ExtendedZoomControls zoomControls = (ExtendedZoomControls) 1379 getZoomControls(); 1380 zoomControls.show(true, canZoomScrollOut()); 1381 zoomControls.requestFocus(); 1382 mPrivateHandler.removeCallbacks(mZoomControlRunnable); 1383 mPrivateHandler.postDelayed(mZoomControlRunnable, 1384 ZOOM_CONTROLS_TIMEOUT); 1385 } 1386 1387 /** 1388 * Return a HitTestResult based on the current focus node. If a HTML::a tag 1389 * is found and the anchor has a non-javascript url, the HitTestResult type 1390 * is set to SRC_ANCHOR_TYPE and the url is set in the "extra" field. If the 1391 * anchor does not have a url or if it is a javascript url, the type will 1392 * be UNKNOWN_TYPE and the url has to be retrieved through 1393 * {@link #requestFocusNodeHref} asynchronously. If a HTML::img tag is 1394 * found, the HitTestResult type is set to IMAGE_TYPE and the url is set in 1395 * the "extra" field. A type of 1396 * SRC_IMAGE_ANCHOR_TYPE indicates an anchor with a url that has an image as 1397 * a child node. If a phone number is found, the HitTestResult type is set 1398 * to PHONE_TYPE and the phone number is set in the "extra" field of 1399 * HitTestResult. If a map address is found, the HitTestResult type is set 1400 * to GEO_TYPE and the address is set in the "extra" field of HitTestResult. 1401 * If an email address is found, the HitTestResult type is set to EMAIL_TYPE 1402 * and the email is set in the "extra" field of HitTestResult. Otherwise, 1403 * HitTestResult type is set to UNKNOWN_TYPE. 1404 */ 1405 public HitTestResult getHitTestResult() { 1406 if (mNativeClass == 0) { 1407 return null; 1408 } 1409 1410 HitTestResult result = new HitTestResult(); 1411 1412 if (nativeUpdateFocusNode()) { 1413 FocusNode node = mFocusNode; 1414 if (node.mIsTextField || node.mIsTextArea) { 1415 result.setType(HitTestResult.EDIT_TEXT_TYPE); 1416 } else if (node.mText != null) { 1417 String text = node.mText; 1418 if (text.startsWith(SCHEME_TEL)) { 1419 result.setType(HitTestResult.PHONE_TYPE); 1420 result.setExtra(text.substring(SCHEME_TEL.length())); 1421 } else if (text.startsWith(SCHEME_MAILTO)) { 1422 result.setType(HitTestResult.EMAIL_TYPE); 1423 result.setExtra(text.substring(SCHEME_MAILTO.length())); 1424 } else if (text.startsWith(SCHEME_GEO)) { 1425 result.setType(HitTestResult.GEO_TYPE); 1426 result.setExtra(URLDecoder.decode(text 1427 .substring(SCHEME_GEO.length()))); 1428 } else if (node.mIsAnchor) { 1429 result.setType(HitTestResult.SRC_ANCHOR_TYPE); 1430 result.setExtra(text); 1431 } 1432 } 1433 } 1434 int type = result.getType(); 1435 if (type == HitTestResult.UNKNOWN_TYPE 1436 || type == HitTestResult.SRC_ANCHOR_TYPE) { 1437 // Now check to see if it is an image. 1438 int contentX = viewToContent((int) mLastTouchX + mScrollX); 1439 int contentY = viewToContent((int) mLastTouchY + mScrollY); 1440 String text = nativeImageURI(contentX, contentY); 1441 if (text != null) { 1442 result.setType(type == HitTestResult.UNKNOWN_TYPE ? 1443 HitTestResult.IMAGE_TYPE : 1444 HitTestResult.SRC_IMAGE_ANCHOR_TYPE); 1445 result.setExtra(text); 1446 } 1447 } 1448 return result; 1449 } 1450 1451 /** 1452 * Request the href of an anchor element due to getFocusNodePath returning 1453 * "href." If hrefMsg is null, this method returns immediately and does not 1454 * dispatch hrefMsg to its target. 1455 * 1456 * @param hrefMsg This message will be dispatched with the result of the 1457 * request as the data member with "url" as key. The result can 1458 * be null. 1459 */ 1460 public void requestFocusNodeHref(Message hrefMsg) { 1461 if (hrefMsg == null || mNativeClass == 0) { 1462 return; 1463 } 1464 if (nativeUpdateFocusNode()) { 1465 FocusNode node = mFocusNode; 1466 if (node.mIsAnchor) { 1467 // NOTE: We may already have the url of the anchor stored in 1468 // node.mText but it may be out of date or the caller may want 1469 // to know about javascript urls. 1470 mWebViewCore.sendMessage(EventHub.REQUEST_FOCUS_HREF, 1471 node.mFramePointer, node.mNodePointer, hrefMsg); 1472 } 1473 } 1474 } 1475 1476 /** 1477 * Request the url of the image last touched by the user. msg will be sent 1478 * to its target with a String representing the url as its object. 1479 * 1480 * @param msg This message will be dispatched with the result of the request 1481 * as the data member with "url" as key. The result can be null. 1482 */ 1483 public void requestImageRef(Message msg) { 1484 int contentX = viewToContent((int) mLastTouchX + mScrollX); 1485 int contentY = viewToContent((int) mLastTouchY + mScrollY); 1486 String ref = nativeImageURI(contentX, contentY); 1487 Bundle data = msg.getData(); 1488 data.putString("url", ref); 1489 msg.setData(data); 1490 msg.sendToTarget(); 1491 } 1492 1493 private static int pinLoc(int x, int viewMax, int docMax) { 1494// Log.d(LOGTAG, "-- pinLoc " + x + " " + viewMax + " " + docMax); 1495 if (docMax < viewMax) { // the doc has room on the sides for "blank" 1496 x = -(viewMax - docMax) >> 1; 1497// Log.d(LOGTAG, "--- center " + x); 1498 } else if (x < 0) { 1499 x = 0; 1500// Log.d(LOGTAG, "--- zero"); 1501 } else if (x + viewMax > docMax) { 1502 x = docMax - viewMax; 1503// Log.d(LOGTAG, "--- pin " + x); 1504 } 1505 return x; 1506 } 1507 1508 // Expects x in view coordinates 1509 private int pinLocX(int x) { 1510 return pinLoc(x, getViewWidth(), computeHorizontalScrollRange()); 1511 } 1512 1513 // Expects y in view coordinates 1514 private int pinLocY(int y) { 1515 return pinLoc(y, getViewHeight(), computeVerticalScrollRange()); 1516 } 1517 1518 /*package*/ int viewToContent(int x) { 1519 return Math.round(x * mInvActualScale); 1520 } 1521 1522 private int contentToView(int x) { 1523 return Math.round(x * mActualScale); 1524 } 1525 1526 // Called by JNI to invalidate the View, given rectangle coordinates in 1527 // content space 1528 private void viewInvalidate(int l, int t, int r, int b) { 1529 invalidate(contentToView(l), contentToView(t), contentToView(r), 1530 contentToView(b)); 1531 } 1532 1533 // Called by JNI to invalidate the View after a delay, given rectangle 1534 // coordinates in content space 1535 private void viewInvalidateDelayed(long delay, int l, int t, int r, int b) { 1536 postInvalidateDelayed(delay, contentToView(l), contentToView(t), 1537 contentToView(r), contentToView(b)); 1538 } 1539 1540 private Rect contentToView(Rect x) { 1541 return new Rect(contentToView(x.left), contentToView(x.top) 1542 , contentToView(x.right), contentToView(x.bottom)); 1543 } 1544 1545 /* call from webcoreview.draw(), so we're still executing in the UI thread 1546 */ 1547 private void recordNewContentSize(int w, int h, boolean updateLayout) { 1548 1549 // premature data from webkit, ignore 1550 if ((w | h) == 0) { 1551 return; 1552 } 1553 1554 // don't abort a scroll animation if we didn't change anything 1555 if (mContentWidth != w || mContentHeight != h) { 1556 // record new dimensions 1557 mContentWidth = w; 1558 mContentHeight = h; 1559 // If history Picture is drawn, don't update scroll. They will be 1560 // updated when we get out of that mode. 1561 if (!mDrawHistory) { 1562 // repin our scroll, taking into account the new content size 1563 int oldX = mScrollX; 1564 int oldY = mScrollY; 1565 mScrollX = pinLocX(mScrollX); 1566 mScrollY = pinLocY(mScrollY); 1567 // android.util.Log.d("skia", "recordNewContentSize - 1568 // abortAnimation"); 1569 mScroller.abortAnimation(); // just in case 1570 if (oldX != mScrollX || oldY != mScrollY) { 1571 sendOurVisibleRect(); 1572 } 1573 } 1574 } 1575 contentSizeChanged(updateLayout); 1576 } 1577 1578 private void setNewZoomScale(float scale, boolean force) { 1579 if (scale < mMinZoomScale) { 1580 scale = mMinZoomScale; 1581 } else if (scale > mMaxZoomScale) { 1582 scale = mMaxZoomScale; 1583 } 1584 if (scale != mActualScale || force) { 1585 if (mDrawHistory) { 1586 // If history Picture is drawn, don't update scroll. They will 1587 // be updated when we get out of that mode. 1588 if (scale != mActualScale) { 1589 mCallbackProxy.onScaleChanged(mActualScale, scale); 1590 } 1591 mActualScale = scale; 1592 mInvActualScale = 1 / scale; 1593 sendViewSizeZoom(); 1594 } else { 1595 // update our scroll so we don't appear to jump 1596 // i.e. keep the center of the doc in the center of the view 1597 1598 int oldX = mScrollX; 1599 int oldY = mScrollY; 1600 float ratio = scale * mInvActualScale; // old inverse 1601 float sx = ratio * oldX + (ratio - 1) * getViewWidth() * 0.5f; 1602 float sy = ratio * oldY + (ratio - 1) * getViewHeight() * 0.5f; 1603 1604 // now update our new scale and inverse 1605 if (scale != mActualScale) { 1606 mCallbackProxy.onScaleChanged(mActualScale, scale); 1607 } 1608 mActualScale = scale; 1609 mInvActualScale = 1 / scale; 1610 1611 // as we don't have animation for scaling, don't do animation 1612 // for scrolling, as it causes weird intermediate state 1613 // pinScrollTo(Math.round(sx), Math.round(sy)); 1614 mScrollX = pinLocX(Math.round(sx)); 1615 mScrollY = pinLocY(Math.round(sy)); 1616 1617 sendViewSizeZoom(); 1618 sendOurVisibleRect(); 1619 } 1620 } 1621 } 1622 1623 // Used to avoid sending many visible rect messages. 1624 private Rect mLastVisibleRectSent; 1625 private Rect mLastGlobalRect; 1626 1627 private Rect sendOurVisibleRect() { 1628 Rect rect = new Rect(); 1629 calcOurContentVisibleRect(rect); 1630 if (mFindIsUp) { 1631 rect.bottom -= viewToContent(FIND_HEIGHT); 1632 } 1633 // Rect.equals() checks for null input. 1634 if (!rect.equals(mLastVisibleRectSent)) { 1635 mWebViewCore.sendMessage(EventHub.SET_SCROLL_OFFSET, 1636 rect.left, rect.top); 1637 mLastVisibleRectSent = rect; 1638 } 1639 Rect globalRect = new Rect(); 1640 if (getGlobalVisibleRect(globalRect) 1641 && !globalRect.equals(mLastGlobalRect)) { 1642 // TODO: the global offset is only used by windowRect() 1643 // in ChromeClientAndroid ; other clients such as touch 1644 // and mouse events could return view + screen relative points. 1645 mWebViewCore.sendMessage(EventHub.SET_GLOBAL_BOUNDS, globalRect); 1646 mLastGlobalRect = globalRect; 1647 } 1648 return rect; 1649 } 1650 1651 // Sets r to be the visible rectangle of our webview in view coordinates 1652 private void calcOurVisibleRect(Rect r) { 1653 Point p = new Point(); 1654 getGlobalVisibleRect(r, p); 1655 r.offset(-p.x, -p.y); 1656 } 1657 1658 // Sets r to be our visible rectangle in content coordinates 1659 private void calcOurContentVisibleRect(Rect r) { 1660 calcOurVisibleRect(r); 1661 r.left = viewToContent(r.left); 1662 r.top = viewToContent(r.top); 1663 r.right = viewToContent(r.right); 1664 r.bottom = viewToContent(r.bottom); 1665 } 1666 1667 /** 1668 * Compute unzoomed width and height, and if they differ from the last 1669 * values we sent, send them to webkit (to be used has new viewport) 1670 * 1671 * @return true if new values were sent 1672 */ 1673 private boolean sendViewSizeZoom() { 1674 int newWidth = Math.round(getViewWidth() * mInvActualScale); 1675 int newHeight = Math.round(getViewHeight() * mInvActualScale); 1676 /* 1677 * Because the native side may have already done a layout before the 1678 * View system was able to measure us, we have to send a height of 0 to 1679 * remove excess whitespace when we grow our width. This will trigger a 1680 * layout and a change in content size. This content size change will 1681 * mean that contentSizeChanged will either call this method directly or 1682 * indirectly from onSizeChanged. 1683 */ 1684 if (newWidth > mLastWidthSent && mWrapContent) { 1685 newHeight = 0; 1686 } 1687 // Avoid sending another message if the dimensions have not changed. 1688 if (newWidth != mLastWidthSent || newHeight != mLastHeightSent) { 1689 mWebViewCore.sendMessage(EventHub.VIEW_SIZE_CHANGED, 1690 newWidth, newHeight, new Float(mActualScale)); 1691 mLastWidthSent = newWidth; 1692 mLastHeightSent = newHeight; 1693 return true; 1694 } 1695 return false; 1696 } 1697 1698 @Override 1699 protected int computeHorizontalScrollRange() { 1700 if (mDrawHistory) { 1701 return mHistoryWidth; 1702 } else { 1703 return contentToView(mContentWidth); 1704 } 1705 } 1706 1707 // Make sure this stays in sync with the actual height of the FindDialog. 1708 private static final int FIND_HEIGHT = 79; 1709 1710 @Override 1711 protected int computeVerticalScrollRange() { 1712 if (mDrawHistory) { 1713 return mHistoryHeight; 1714 } else { 1715 int height = contentToView(mContentHeight); 1716 if (mFindIsUp) { 1717 height += FIND_HEIGHT; 1718 } 1719 return height; 1720 } 1721 } 1722 1723 /** 1724 * Get the url for the current page. This is not always the same as the url 1725 * passed to WebViewClient.onPageStarted because although the load for 1726 * that url has begun, the current page may not have changed. 1727 * @return The url for the current page. 1728 */ 1729 public String getUrl() { 1730 WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); 1731 return h != null ? h.getUrl() : null; 1732 } 1733 1734 /** 1735 * Get the original url for the current page. This is not always the same 1736 * as the url passed to WebViewClient.onPageStarted because although the 1737 * load for that url has begun, the current page may not have changed. 1738 * Also, there may have been redirects resulting in a different url to that 1739 * originally requested. 1740 * @return The url that was originally requested for the current page. 1741 * 1742 * @hide pending API Council approval 1743 */ 1744 public String getOriginalUrl() { 1745 WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); 1746 return h != null ? h.getOriginalUrl() : null; 1747 } 1748 1749 /** 1750 * Get the title for the current page. This is the title of the current page 1751 * until WebViewClient.onReceivedTitle is called. 1752 * @return The title for the current page. 1753 */ 1754 public String getTitle() { 1755 WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); 1756 return h != null ? h.getTitle() : null; 1757 } 1758 1759 /** 1760 * Get the favicon for the current page. This is the favicon of the current 1761 * page until WebViewClient.onReceivedIcon is called. 1762 * @return The favicon for the current page. 1763 */ 1764 public Bitmap getFavicon() { 1765 WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); 1766 return h != null ? h.getFavicon() : null; 1767 } 1768 1769 /** 1770 * Get the progress for the current page. 1771 * @return The progress for the current page between 0 and 100. 1772 */ 1773 public int getProgress() { 1774 return mCallbackProxy.getProgress(); 1775 } 1776 1777 /** 1778 * @return the height of the HTML content. 1779 */ 1780 public int getContentHeight() { 1781 return mContentHeight; 1782 } 1783 1784 /** 1785 * Pause all layout, parsing, and javascript timers. This can be useful if 1786 * the WebView is not visible or the application has been paused. 1787 */ 1788 public void pauseTimers() { 1789 mWebViewCore.sendMessage(EventHub.PAUSE_TIMERS); 1790 } 1791 1792 /** 1793 * Resume all layout, parsing, and javascript timers. This will resume 1794 * dispatching all timers. 1795 */ 1796 public void resumeTimers() { 1797 mWebViewCore.sendMessage(EventHub.RESUME_TIMERS); 1798 } 1799 1800 /** 1801 * Clear the resource cache. Note that the cache is per-application, so 1802 * this will clear the cache for all WebViews used. 1803 * 1804 * @param includeDiskFiles If false, only the RAM cache is cleared. 1805 */ 1806 public void clearCache(boolean includeDiskFiles) { 1807 // Note: this really needs to be a static method as it clears cache for all 1808 // WebView. But we need mWebViewCore to send message to WebCore thread, so 1809 // we can't make this static. 1810 mWebViewCore.sendMessage(EventHub.CLEAR_CACHE, 1811 includeDiskFiles ? 1 : 0, 0); 1812 } 1813 1814 /** 1815 * Make sure that clearing the form data removes the adapter from the 1816 * currently focused textfield if there is one. 1817 */ 1818 public void clearFormData() { 1819 if (inEditingMode()) { 1820 ArrayAdapter<String> adapter = null; 1821 mTextEntry.setAdapter(adapter); 1822 } 1823 } 1824 1825 /** 1826 * Tell the WebView to clear its internal back/forward list. 1827 */ 1828 public void clearHistory() { 1829 mCallbackProxy.getBackForwardList().setClearPending(); 1830 mWebViewCore.sendMessage(EventHub.CLEAR_HISTORY); 1831 } 1832 1833 /** 1834 * Clear the SSL preferences table stored in response to proceeding with SSL 1835 * certificate errors. 1836 */ 1837 public void clearSslPreferences() { 1838 mWebViewCore.sendMessage(EventHub.CLEAR_SSL_PREF_TABLE); 1839 } 1840 1841 /** 1842 * Return the WebBackForwardList for this WebView. This contains the 1843 * back/forward list for use in querying each item in the history stack. 1844 * This is a copy of the private WebBackForwardList so it contains only a 1845 * snapshot of the current state. Multiple calls to this method may return 1846 * different objects. The object returned from this method will not be 1847 * updated to reflect any new state. 1848 */ 1849 public WebBackForwardList copyBackForwardList() { 1850 return mCallbackProxy.getBackForwardList().clone(); 1851 } 1852 1853 /* 1854 * Highlight and scroll to the next occurance of String in findAll. 1855 * Wraps the page infinitely, and scrolls. Must be called after 1856 * calling findAll. 1857 * 1858 * @param forward Direction to search. 1859 */ 1860 public void findNext(boolean forward) { 1861 nativeFindNext(forward); 1862 } 1863 1864 /* 1865 * Find all instances of find on the page and highlight them. 1866 * @param find String to find. 1867 * @return int The number of occurances of the String "find" 1868 * that were found. 1869 */ 1870 public int findAll(String find) { 1871 mFindIsUp = true; 1872 int result = nativeFindAll(find.toLowerCase(), find.toUpperCase()); 1873 invalidate(); 1874 return result; 1875 } 1876 1877 // Used to know whether the find dialog is open. Affects whether 1878 // or not we draw the highlights for matches. 1879 private boolean mFindIsUp; 1880 1881 private native int nativeFindAll(String findLower, String findUpper); 1882 private native void nativeFindNext(boolean forward); 1883 1884 /** 1885 * Return the first substring consisting of the address of a physical 1886 * location. Currently, only addresses in the United States are detected, 1887 * and consist of: 1888 * - a house number 1889 * - a street name 1890 * - a street type (Road, Circle, etc), either spelled out or abbreviated 1891 * - a city name 1892 * - a state or territory, either spelled out or two-letter abbr. 1893 * - an optional 5 digit or 9 digit zip code. 1894 * 1895 * All names must be correctly capitalized, and the zip code, if present, 1896 * must be valid for the state. The street type must be a standard USPS 1897 * spelling or abbreviation. The state or territory must also be spelled 1898 * or abbreviated using USPS standards. The house number may not exceed 1899 * five digits. 1900 * @param addr The string to search for addresses. 1901 * 1902 * @return the address, or if no address is found, return null. 1903 */ 1904 public static String findAddress(String addr) { 1905 return WebViewCore.nativeFindAddress(addr); 1906 } 1907 1908 /* 1909 * Clear the highlighting surrounding text matches created by findAll. 1910 */ 1911 public void clearMatches() { 1912 mFindIsUp = false; 1913 nativeSetFindIsDown(); 1914 // Now that the dialog has been removed, ensure that we scroll to a 1915 // location that is not beyond the end of the page. 1916 pinScrollTo(mScrollX, mScrollY, false); 1917 invalidate(); 1918 } 1919 1920 /** 1921 * Query the document to see if it contains any image references. The 1922 * message object will be dispatched with arg1 being set to 1 if images 1923 * were found and 0 if the document does not reference any images. 1924 * @param response The message that will be dispatched with the result. 1925 */ 1926 public void documentHasImages(Message response) { 1927 if (response == null) { 1928 return; 1929 } 1930 mWebViewCore.sendMessage(EventHub.DOC_HAS_IMAGES, response); 1931 } 1932 1933 @Override 1934 public void computeScroll() { 1935 if (mScroller.computeScrollOffset()) { 1936 int oldX = mScrollX; 1937 int oldY = mScrollY; 1938 mScrollX = mScroller.getCurrX(); 1939 mScrollY = mScroller.getCurrY(); 1940 postInvalidate(); // So we draw again 1941 if (oldX != mScrollX || oldY != mScrollY) { 1942 // as onScrollChanged() is not called, sendOurVisibleRect() 1943 // needs to be call explicitly 1944 sendOurVisibleRect(); 1945 } 1946 } else { 1947 super.computeScroll(); 1948 } 1949 } 1950 1951 private static int computeDuration(int dx, int dy) { 1952 int distance = Math.max(Math.abs(dx), Math.abs(dy)); 1953 int duration = distance * 1000 / STD_SPEED; 1954 return Math.min(duration, MAX_DURATION); 1955 } 1956 1957 // helper to pin the scrollBy parameters (already in view coordinates) 1958 // returns true if the scroll was changed 1959 private boolean pinScrollBy(int dx, int dy, boolean animate) { 1960 return pinScrollTo(mScrollX + dx, mScrollY + dy, animate); 1961 } 1962 1963 // helper to pin the scrollTo parameters (already in view coordinates) 1964 // returns true if the scroll was changed 1965 private boolean pinScrollTo(int x, int y, boolean animate) { 1966 x = pinLocX(x); 1967 y = pinLocY(y); 1968 int dx = x - mScrollX; 1969 int dy = y - mScrollY; 1970 1971 if ((dx | dy) == 0) { 1972 return false; 1973 } 1974 1975 if (true && animate) { 1976 // Log.d(LOGTAG, "startScroll: " + dx + " " + dy); 1977 1978 mScroller.startScroll(mScrollX, mScrollY, dx, dy, 1979 computeDuration(dx, dy)); 1980 invalidate(); 1981 } else { 1982 mScroller.abortAnimation(); // just in case 1983 scrollTo(x, y); 1984 } 1985 return true; 1986 } 1987 1988 // Scale from content to view coordinates, and pin. 1989 // Also called by jni webview.cpp 1990 private void setContentScrollBy(int cx, int cy) { 1991 if (mDrawHistory) { 1992 // disallow WebView to change the scroll position as History Picture 1993 // is used in the view system. 1994 // TODO: as we switchOutDrawHistory when trackball or navigation 1995 // keys are hit, this should be safe. Right? 1996 return; 1997 } 1998 cx = contentToView(cx); 1999 cy = contentToView(cy); 2000 if (mHeightCanMeasure) { 2001 // move our visible rect according to scroll request 2002 if (cy != 0) { 2003 Rect tempRect = new Rect(); 2004 calcOurVisibleRect(tempRect); 2005 tempRect.offset(cx, cy); 2006 requestRectangleOnScreen(tempRect); 2007 } 2008 // FIXME: We scroll horizontally no matter what because currently 2009 // ScrollView and ListView will not scroll horizontally. 2010 // FIXME: Why do we only scroll horizontally if there is no 2011 // vertical scroll? 2012// Log.d(LOGTAG, "setContentScrollBy cy=" + cy); 2013 if (cy == 0 && cx != 0) { 2014 pinScrollBy(cx, 0, true); 2015 } 2016 } else { 2017 pinScrollBy(cx, cy, true); 2018 } 2019 } 2020 2021 // scale from content to view coordinates, and pin 2022 // return true if pin caused the final x/y different than the request cx/cy; 2023 // return false if the view scroll to the exact position as it is requested. 2024 private boolean setContentScrollTo(int cx, int cy) { 2025 if (mDrawHistory) { 2026 // disallow WebView to change the scroll position as History Picture 2027 // is used in the view system. 2028 // One known case where this is called is that WebCore tries to 2029 // restore the scroll position. As history Picture already uses the 2030 // saved scroll position, it is ok to skip this. 2031 return false; 2032 } 2033 int vx = contentToView(cx); 2034 int vy = contentToView(cy); 2035// Log.d(LOGTAG, "content scrollTo [" + cx + " " + cy + "] view=[" + 2036// vx + " " + vy + "]"); 2037 pinScrollTo(vx, vy, false); 2038 if (mScrollX != vx || mScrollY != vy) { 2039 return true; 2040 } else { 2041 return false; 2042 } 2043 } 2044 2045 // scale from content to view coordinates, and pin 2046 private void spawnContentScrollTo(int cx, int cy) { 2047 if (mDrawHistory) { 2048 // disallow WebView to change the scroll position as History Picture 2049 // is used in the view system. 2050 return; 2051 } 2052 int vx = contentToView(cx); 2053 int vy = contentToView(cy); 2054 pinScrollTo(vx, vy, true); 2055 } 2056 2057 /** 2058 * These are from webkit, and are in content coordinate system (unzoomed) 2059 */ 2060 private void contentSizeChanged(boolean updateLayout) { 2061 // suppress 0,0 since we usually see real dimensions soon after 2062 // this avoids drawing the prev content in a funny place. If we find a 2063 // way to consolidate these notifications, this check may become 2064 // obsolete 2065 if ((mContentWidth | mContentHeight) == 0) { 2066 return; 2067 } 2068 2069 if (mHeightCanMeasure) { 2070 if (getMeasuredHeight() != contentToView(mContentHeight) 2071 && updateLayout) { 2072 requestLayout(); 2073 } 2074 } else if (mWidthCanMeasure) { 2075 if (getMeasuredWidth() != contentToView(mContentWidth) 2076 && updateLayout) { 2077 requestLayout(); 2078 } 2079 } else { 2080 // If we don't request a layout, try to send our view size to the 2081 // native side to ensure that WebCore has the correct dimensions. 2082 sendViewSizeZoom(); 2083 } 2084 } 2085 2086 /** 2087 * Set the WebViewClient that will receive various notifications and 2088 * requests. This will replace the current handler. 2089 * @param client An implementation of WebViewClient. 2090 */ 2091 public void setWebViewClient(WebViewClient client) { 2092 mCallbackProxy.setWebViewClient(client); 2093 } 2094 2095 /** 2096 * Register the interface to be used when content can not be handled by 2097 * the rendering engine, and should be downloaded instead. This will replace 2098 * the current handler. 2099 * @param listener An implementation of DownloadListener. 2100 */ 2101 public void setDownloadListener(DownloadListener listener) { 2102 mCallbackProxy.setDownloadListener(listener); 2103 } 2104 2105 /** 2106 * Set the chrome handler. This is an implementation of WebChromeClient for 2107 * use in handling Javascript dialogs, favicons, titles, and the progress. 2108 * This will replace the current handler. 2109 * @param client An implementation of WebChromeClient. 2110 */ 2111 public void setWebChromeClient(WebChromeClient client) { 2112 mCallbackProxy.setWebChromeClient(client); 2113 } 2114 2115 /** 2116 * Set the Picture listener. This is an interface used to receive 2117 * notifications of a new Picture. 2118 * @param listener An implementation of WebView.PictureListener. 2119 */ 2120 public void setPictureListener(PictureListener listener) { 2121 mPictureListener = listener; 2122 } 2123 2124 /** 2125 * {@hide} 2126 */ 2127 /* FIXME: Debug only! Remove for SDK! */ 2128 public void externalRepresentation(Message callback) { 2129 mWebViewCore.sendMessage(EventHub.REQUEST_EXT_REPRESENTATION, callback); 2130 } 2131 2132 /** 2133 * {@hide} 2134 */ 2135 /* FIXME: Debug only! Remove for SDK! */ 2136 public void documentAsText(Message callback) { 2137 mWebViewCore.sendMessage(EventHub.REQUEST_DOC_AS_TEXT, callback); 2138 } 2139 2140 /** 2141 * Use this function to bind an object to Javascript so that the 2142 * methods can be accessed from Javascript. 2143 * IMPORTANT, the object that is bound runs in another thread and 2144 * not in the thread that it was constructed in. 2145 * @param obj The class instance to bind to Javascript 2146 * @param interfaceName The name to used to expose the class in Javascript 2147 */ 2148 public void addJavascriptInterface(Object obj, String interfaceName) { 2149 // Use Hashmap rather than Bundle as Bundles can't cope with Objects 2150 HashMap arg = new HashMap(); 2151 arg.put("object", obj); 2152 arg.put("interfaceName", interfaceName); 2153 mWebViewCore.sendMessage(EventHub.ADD_JS_INTERFACE, arg); 2154 } 2155 2156 /** 2157 * Return the WebSettings object used to control the settings for this 2158 * WebView. 2159 * @return A WebSettings object that can be used to control this WebView's 2160 * settings. 2161 */ 2162 public WebSettings getSettings() { 2163 return mWebViewCore.getSettings(); 2164 } 2165 2166 /** 2167 * Return the list of currently loaded plugins. 2168 * @return The list of currently loaded plugins. 2169 */ 2170 public static synchronized PluginList getPluginList() { 2171 if (sPluginList == null) { 2172 sPluginList = new PluginList(); 2173 } 2174 return sPluginList; 2175 } 2176 2177 /** 2178 * Signal the WebCore thread to refresh its list of plugins. Use 2179 * this if the directory contents of one of the plugin directories 2180 * has been modified and needs its changes reflecting. May cause 2181 * plugin load and/or unload. 2182 * @param reloadOpenPages Set to true to reload all open pages. 2183 */ 2184 public void refreshPlugins(boolean reloadOpenPages) { 2185 if (mWebViewCore != null) { 2186 mWebViewCore.sendMessage(EventHub.REFRESH_PLUGINS, reloadOpenPages); 2187 } 2188 } 2189 2190 //------------------------------------------------------------------------- 2191 // Override View methods 2192 //------------------------------------------------------------------------- 2193 2194 @Override 2195 protected void finalize() throws Throwable { 2196 destroy(); 2197 } 2198 2199 @Override 2200 protected void onDraw(Canvas canvas) { 2201 // if mNativeClass is 0, the WebView has been destroyed. Do nothing. 2202 if (mNativeClass == 0) { 2203 return; 2204 } 2205 if (mWebViewCore.mEndScaleZoom) { 2206 mWebViewCore.mEndScaleZoom = false; 2207 if (mTouchMode >= FIRST_SCROLL_ZOOM 2208 && mTouchMode <= LAST_SCROLL_ZOOM) { 2209 setHorizontalScrollBarEnabled(true); 2210 setVerticalScrollBarEnabled(true); 2211 mTouchMode = TOUCH_DONE_MODE; 2212 } 2213 } 2214 int sc = canvas.save(); 2215 if (mTouchMode >= FIRST_SCROLL_ZOOM && mTouchMode <= LAST_SCROLL_ZOOM) { 2216 scrollZoomDraw(canvas); 2217 } else { 2218 nativeRecomputeFocus(); 2219 // Update the buttons in the picture, so when we draw the picture 2220 // to the screen, they are in the correct state. 2221 // Tell the native side if user is a) touching the screen, 2222 // b) pressing the trackball down, or c) pressing the enter key 2223 // If the focus is a button, we need to draw it in the pressed 2224 // state. 2225 // If mNativeClass is 0, we should not reach here, so we do not 2226 // need to check it again. 2227 nativeRecordButtons(mTouchMode == TOUCH_SHORTPRESS_START_MODE 2228 || mTrackballDown || mGotEnterDown, false); 2229 drawCoreAndFocusRing(canvas, mBackgroundColor, mDrawFocusRing); 2230 } 2231 canvas.restoreToCount(sc); 2232 2233 if (AUTO_REDRAW_HACK && mAutoRedraw) { 2234 invalidate(); 2235 } 2236 } 2237 2238 @Override 2239 public void setLayoutParams(ViewGroup.LayoutParams params) { 2240 if (params.height == LayoutParams.WRAP_CONTENT) { 2241 mWrapContent = true; 2242 } 2243 super.setLayoutParams(params); 2244 } 2245 2246 @Override 2247 public boolean performLongClick() { 2248 if (inEditingMode()) { 2249 return mTextEntry.performLongClick(); 2250 } else { 2251 return super.performLongClick(); 2252 } 2253 } 2254 2255 private void drawCoreAndFocusRing(Canvas canvas, int color, 2256 boolean drawFocus) { 2257 if (mDrawHistory) { 2258 canvas.scale(mActualScale, mActualScale); 2259 canvas.drawPicture(mHistoryPicture); 2260 return; 2261 } 2262 2263 boolean animateZoom = mZoomScale != 0; 2264 boolean animateScroll = !mScroller.isFinished() 2265 || mVelocityTracker != null; 2266 if (animateZoom) { 2267 float zoomScale; 2268 int interval = (int) (SystemClock.uptimeMillis() - mZoomStart); 2269 if (interval < ZOOM_ANIMATION_LENGTH) { 2270 float ratio = (float) interval / ZOOM_ANIMATION_LENGTH; 2271 zoomScale = 1.0f / (mInvInitialZoomScale 2272 + (mInvFinalZoomScale - mInvInitialZoomScale) * ratio); 2273 invalidate(); 2274 } else { 2275 zoomScale = mZoomScale; 2276 } 2277 float scale = (mActualScale - zoomScale) * mInvActualScale; 2278 float tx = scale * ((getLeft() + getRight()) * 0.5f + mScrollX); 2279 float ty = scale * ((getTop() + getBottom()) * 0.5f + mScrollY); 2280 2281 // this block pins the translate to "legal" bounds. This makes the 2282 // animation a bit non-obvious, but it means we won't pop when the 2283 // "real" zoom takes effect 2284 if (true) { 2285 // canvas.translate(mScrollX, mScrollY); 2286 tx -= mScrollX; 2287 ty -= mScrollY; 2288 tx = -pinLoc(-Math.round(tx), getViewWidth(), Math 2289 .round(mContentWidth * zoomScale)); 2290 ty = -pinLoc(-Math.round(ty), getViewHeight(), Math 2291 .round(mContentHeight * zoomScale)); 2292 tx += mScrollX; 2293 ty += mScrollY; 2294 } 2295 canvas.translate(tx, ty); 2296 canvas.scale(zoomScale, zoomScale); 2297 } else { 2298 canvas.scale(mActualScale, mActualScale); 2299 } 2300 2301 mWebViewCore.drawContentPicture(canvas, color, animateZoom, 2302 animateScroll); 2303 2304 if (mNativeClass == 0) return; 2305 if (mShiftIsPressed) { 2306 if (mTouchSelection) { 2307 nativeDrawSelectionRegion(canvas); 2308 } else { 2309 nativeDrawSelection(canvas, mSelectX, mSelectY, 2310 mExtendSelection); 2311 } 2312 } else if (drawFocus) { 2313 if (mTouchMode == TOUCH_SHORTPRESS_START_MODE) { 2314 mTouchMode = TOUCH_SHORTPRESS_MODE; 2315 HitTestResult hitTest = getHitTestResult(); 2316 if (hitTest != null && 2317 hitTest.mType != HitTestResult.UNKNOWN_TYPE) { 2318 mPrivateHandler.sendMessageDelayed(mPrivateHandler 2319 .obtainMessage(SWITCH_TO_LONGPRESS), 2320 LONG_PRESS_TIMEOUT); 2321 } 2322 } 2323 nativeDrawFocusRing(canvas); 2324 } 2325 // When the FindDialog is up, only draw the matches if we are not in 2326 // the process of scrolling them into view. 2327 if (mFindIsUp && !animateScroll) { 2328 nativeDrawMatches(canvas); 2329 } 2330 } 2331 2332 private native void nativeDrawMatches(Canvas canvas); 2333 2334 private float scrollZoomGridScale(float invScale) { 2335 float griddedInvScale = (int) (invScale * SCROLL_ZOOM_GRID) 2336 / (float) SCROLL_ZOOM_GRID; 2337 return 1.0f / griddedInvScale; 2338 } 2339 2340 private float scrollZoomX(float scale) { 2341 int width = getViewWidth(); 2342 float maxScrollZoomX = mContentWidth * scale - width; 2343 int maxX = mContentWidth - width; 2344 return -(maxScrollZoomX > 0 ? mZoomScrollX * maxScrollZoomX / maxX 2345 : maxScrollZoomX / 2); 2346 } 2347 2348 private float scrollZoomY(float scale) { 2349 int height = getViewHeight(); 2350 float maxScrollZoomY = mContentHeight * scale - height; 2351 int maxY = mContentHeight - height; 2352 return -(maxScrollZoomY > 0 ? mZoomScrollY * maxScrollZoomY / maxY 2353 : maxScrollZoomY / 2); 2354 } 2355 2356 private void drawMagnifyFrame(Canvas canvas, Rect frame, Paint paint) { 2357 final float ADORNMENT_LEN = 16.0f; 2358 float width = frame.width(); 2359 float height = frame.height(); 2360 Path path = new Path(); 2361 path.moveTo(-ADORNMENT_LEN, -ADORNMENT_LEN); 2362 path.lineTo(0, 0); 2363 path.lineTo(width, 0); 2364 path.lineTo(width + ADORNMENT_LEN, -ADORNMENT_LEN); 2365 path.moveTo(-ADORNMENT_LEN, height + ADORNMENT_LEN); 2366 path.lineTo(0, height); 2367 path.lineTo(width, height); 2368 path.lineTo(width + ADORNMENT_LEN, height + ADORNMENT_LEN); 2369 path.moveTo(0, 0); 2370 path.lineTo(0, height); 2371 path.moveTo(width, 0); 2372 path.lineTo(width, height); 2373 path.offset(frame.left, frame.top); 2374 canvas.drawPath(path, paint); 2375 } 2376 2377 // Returns frame surrounding magified portion of screen while 2378 // scroll-zoom is enabled. The frame is also used to center the 2379 // zoom-in zoom-out points at the start and end of the animation. 2380 private Rect scrollZoomFrame(int width, int height, float halfScale) { 2381 Rect scrollFrame = new Rect(); 2382 scrollFrame.set(mZoomScrollX, mZoomScrollY, 2383 mZoomScrollX + width, mZoomScrollY + height); 2384 if (mContentWidth * mZoomScrollLimit < width) { 2385 float scale = zoomFrameScaleX(width, halfScale, 1.0f); 2386 float offsetX = (width * scale - width) * 0.5f; 2387 scrollFrame.left -= offsetX; 2388 scrollFrame.right += offsetX; 2389 } 2390 if (mContentHeight * mZoomScrollLimit < height) { 2391 float scale = zoomFrameScaleY(height, halfScale, 1.0f); 2392 float offsetY = (height * scale - height) * 0.5f; 2393 scrollFrame.top -= offsetY; 2394 scrollFrame.bottom += offsetY; 2395 } 2396 return scrollFrame; 2397 } 2398 2399 private float zoomFrameScaleX(int width, float halfScale, float noScale) { 2400 // mContentWidth > width > mContentWidth * mZoomScrollLimit 2401 if (mContentWidth <= width) { 2402 return halfScale; 2403 } 2404 float part = (width - mContentWidth * mZoomScrollLimit) 2405 / (width * (1 - mZoomScrollLimit)); 2406 return halfScale * part + noScale * (1.0f - part); 2407 } 2408 2409 private float zoomFrameScaleY(int height, float halfScale, float noScale) { 2410 if (mContentHeight <= height) { 2411 return halfScale; 2412 } 2413 float part = (height - mContentHeight * mZoomScrollLimit) 2414 / (height * (1 - mZoomScrollLimit)); 2415 return halfScale * part + noScale * (1.0f - part); 2416 } 2417 2418 private float scrollZoomMagScale(float invScale) { 2419 return (invScale * 2 + mInvActualScale) / 3; 2420 } 2421 2422 private void scrollZoomDraw(Canvas canvas) { 2423 float invScale = mZoomScrollInvLimit; 2424 int elapsed = 0; 2425 if (mTouchMode != SCROLL_ZOOM_OUT) { 2426 elapsed = (int) Math.min(System.currentTimeMillis() 2427 - mZoomScrollStart, SCROLL_ZOOM_DURATION); 2428 float transitionScale = (mZoomScrollInvLimit - mInvActualScale) 2429 * elapsed / SCROLL_ZOOM_DURATION; 2430 if (mTouchMode == SCROLL_ZOOM_ANIMATION_OUT) { 2431 invScale = mInvActualScale + transitionScale; 2432 } else { /* if (mTouchMode == SCROLL_ZOOM_ANIMATION_IN) */ 2433 invScale = mZoomScrollInvLimit - transitionScale; 2434 } 2435 } 2436 float scale = scrollZoomGridScale(invScale); 2437 invScale = 1.0f / scale; 2438 int width = getViewWidth(); 2439 int height = getViewHeight(); 2440 float halfScale = scrollZoomMagScale(invScale); 2441 Rect scrollFrame = scrollZoomFrame(width, height, halfScale); 2442 if (elapsed == SCROLL_ZOOM_DURATION) { 2443 if (mTouchMode == SCROLL_ZOOM_ANIMATION_IN) { 2444 setHorizontalScrollBarEnabled(true); 2445 setVerticalScrollBarEnabled(true); 2446 updateTextEntry(); 2447 scrollTo((int) (scrollFrame.centerX() * mActualScale) 2448 - (width >> 1), (int) (scrollFrame.centerY() 2449 * mActualScale) - (height >> 1)); 2450 mTouchMode = TOUCH_DONE_MODE; 2451 } else { 2452 mTouchMode = SCROLL_ZOOM_OUT; 2453 } 2454 } 2455 float newX = scrollZoomX(scale); 2456 float newY = scrollZoomY(scale); 2457 if (LOGV_ENABLED) { 2458 Log.v(LOGTAG, "scrollZoomDraw scale=" + scale + " + (" + newX 2459 + ", " + newY + ") mZoomScroll=(" + mZoomScrollX + ", " 2460 + mZoomScrollY + ")" + " invScale=" + invScale + " scale=" 2461 + scale); 2462 } 2463 canvas.translate(newX, newY); 2464 canvas.scale(scale, scale); 2465 boolean animating = mTouchMode != SCROLL_ZOOM_OUT; 2466 if (mDrawHistory) { 2467 int sc = canvas.save(Canvas.CLIP_SAVE_FLAG); 2468 Rect clip = new Rect(0, 0, mHistoryPicture.getWidth(), 2469 mHistoryPicture.getHeight()); 2470 canvas.clipRect(clip, Region.Op.DIFFERENCE); 2471 canvas.drawColor(mBackgroundColor); 2472 canvas.restoreToCount(sc); 2473 canvas.drawPicture(mHistoryPicture); 2474 } else { 2475 mWebViewCore.drawContentPicture(canvas, mBackgroundColor, 2476 animating, true); 2477 } 2478 if (mTouchMode == TOUCH_DONE_MODE) { 2479 return; 2480 } 2481 Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); 2482 paint.setStyle(Paint.Style.STROKE); 2483 paint.setStrokeWidth(30.0f); 2484 paint.setARGB(0x50, 0, 0, 0); 2485 int maxX = mContentWidth - width; 2486 int maxY = mContentHeight - height; 2487 if (true) { // experiment: draw hint to place finger off magnify area 2488 drawMagnifyFrame(canvas, scrollFrame, paint); 2489 } else { 2490 canvas.drawRect(scrollFrame, paint); 2491 } 2492 int sc = canvas.save(); 2493 canvas.clipRect(scrollFrame); 2494 float halfX = (float) mZoomScrollX / maxX; 2495 if (mContentWidth * mZoomScrollLimit < width) { 2496 halfX = zoomFrameScaleX(width, 0.5f, halfX); 2497 } 2498 float halfY = (float) mZoomScrollY / maxY; 2499 if (mContentHeight * mZoomScrollLimit < height) { 2500 halfY = zoomFrameScaleY(height, 0.5f, halfY); 2501 } 2502 canvas.scale(halfScale, halfScale, mZoomScrollX + width * halfX 2503 , mZoomScrollY + height * halfY); 2504 if (LOGV_ENABLED) { 2505 Log.v(LOGTAG, "scrollZoomDraw halfScale=" + halfScale + " w/h=(" 2506 + width + ", " + height + ") half=(" + halfX + ", " 2507 + halfY + ")"); 2508 } 2509 if (mDrawHistory) { 2510 canvas.drawPicture(mHistoryPicture); 2511 } else { 2512 mWebViewCore.drawContentPicture(canvas, mBackgroundColor, 2513 animating, false); 2514 } 2515 canvas.restoreToCount(sc); 2516 if (mTouchMode != SCROLL_ZOOM_OUT) { 2517 invalidate(); 2518 } 2519 } 2520 2521 private void zoomScrollTap(float x, float y) { 2522 float scale = scrollZoomGridScale(mZoomScrollInvLimit); 2523 float left = scrollZoomX(scale); 2524 float top = scrollZoomY(scale); 2525 int width = getViewWidth(); 2526 int height = getViewHeight(); 2527 x -= width * scale / 2; 2528 y -= height * scale / 2; 2529 mZoomScrollX = Math.min(mContentWidth - width 2530 , Math.max(0, (int) ((x - left) / scale))); 2531 mZoomScrollY = Math.min(mContentHeight - height 2532 , Math.max(0, (int) ((y - top) / scale))); 2533 if (LOGV_ENABLED) { 2534 Log.v(LOGTAG, "zoomScrollTap scale=" + scale + " + (" + left 2535 + ", " + top + ") mZoomScroll=(" + mZoomScrollX + ", " 2536 + mZoomScrollY + ")" + " x=" + x + " y=" + y); 2537 } 2538 } 2539 2540 private boolean canZoomScrollOut() { 2541 if (mContentWidth == 0 || mContentHeight == 0) { 2542 return false; 2543 } 2544 int width = getViewWidth(); 2545 int height = getViewHeight(); 2546 float x = (float) width / (float) mContentWidth; 2547 float y = (float) height / (float) mContentHeight; 2548 mZoomScrollLimit = Math.max(DEFAULT_MIN_ZOOM_SCALE, Math.min(x, y)); 2549 mZoomScrollInvLimit = 1.0f / mZoomScrollLimit; 2550 if (LOGV_ENABLED) { 2551 Log.v(LOGTAG, "canZoomScrollOut" 2552 + " mInvActualScale=" + mInvActualScale 2553 + " mZoomScrollLimit=" + mZoomScrollLimit 2554 + " mZoomScrollInvLimit=" + mZoomScrollInvLimit 2555 + " mContentWidth=" + mContentWidth 2556 + " mContentHeight=" + mContentHeight 2557 ); 2558 } 2559 // don't zoom out unless magnify area is at least half as wide 2560 // or tall as content 2561 float limit = mZoomScrollLimit * 2; 2562 return mContentWidth >= width * limit 2563 || mContentHeight >= height * limit; 2564 } 2565 2566 private void startZoomScrollOut() { 2567 setHorizontalScrollBarEnabled(false); 2568 setVerticalScrollBarEnabled(false); 2569 if (mZoomControlRunnable != null) { 2570 mPrivateHandler.removeCallbacks(mZoomControlRunnable); 2571 } 2572 if (mZoomControls != null) { 2573 mZoomControls.hide(); 2574 } 2575 int width = getViewWidth(); 2576 int height = getViewHeight(); 2577 int halfW = width >> 1; 2578 mLastTouchX = halfW; 2579 int halfH = height >> 1; 2580 mLastTouchY = halfH; 2581 mScroller.abortAnimation(); 2582 mZoomScrollStart = System.currentTimeMillis(); 2583 Rect zoomFrame = scrollZoomFrame(width, height 2584 , scrollZoomMagScale(mZoomScrollInvLimit)); 2585 mZoomScrollX = Math.max(0, (int) ((mScrollX + halfW) * mInvActualScale) 2586 - (zoomFrame.width() >> 1)); 2587 mZoomScrollY = Math.max(0, (int) ((mScrollY + halfH) * mInvActualScale) 2588 - (zoomFrame.height() >> 1)); 2589 scrollTo(0, 0); // triggers inval, starts animation 2590 clearTextEntry(); 2591 if (LOGV_ENABLED) { 2592 Log.v(LOGTAG, "startZoomScrollOut mZoomScroll=(" 2593 + mZoomScrollX + ", " + mZoomScrollY +")"); 2594 } 2595 } 2596 2597 private void zoomScrollOut() { 2598 if (canZoomScrollOut() == false) { 2599 mTouchMode = TOUCH_DONE_MODE; 2600 return; 2601 } 2602 startZoomScrollOut(); 2603 mTouchMode = SCROLL_ZOOM_ANIMATION_OUT; 2604 invalidate(); 2605 } 2606 2607 private void moveZoomScrollWindow(float x, float y) { 2608 if (Math.abs(x - mLastZoomScrollRawX) < 1.5f 2609 && Math.abs(y - mLastZoomScrollRawY) < 1.5f) { 2610 return; 2611 } 2612 mLastZoomScrollRawX = x; 2613 mLastZoomScrollRawY = y; 2614 int oldX = mZoomScrollX; 2615 int oldY = mZoomScrollY; 2616 int width = getViewWidth(); 2617 int height = getViewHeight(); 2618 int maxZoomX = mContentWidth - width; 2619 if (maxZoomX > 0) { 2620 int maxScreenX = width - (int) Math.ceil(width 2621 * mZoomScrollLimit) - SCROLL_ZOOM_FINGER_BUFFER; 2622 if (LOGV_ENABLED) { 2623 Log.v(LOGTAG, "moveZoomScrollWindow-X" 2624 + " maxScreenX=" + maxScreenX + " width=" + width 2625 + " mZoomScrollLimit=" + mZoomScrollLimit + " x=" + x); 2626 } 2627 x += maxScreenX * mLastScrollX / maxZoomX - mLastTouchX; 2628 x *= Math.max(maxZoomX / maxScreenX, mZoomScrollInvLimit); 2629 mZoomScrollX = Math.max(0, Math.min(maxZoomX, (int) x)); 2630 } 2631 int maxZoomY = mContentHeight - height; 2632 if (maxZoomY > 0) { 2633 int maxScreenY = height - (int) Math.ceil(height 2634 * mZoomScrollLimit) - SCROLL_ZOOM_FINGER_BUFFER; 2635 if (LOGV_ENABLED) { 2636 Log.v(LOGTAG, "moveZoomScrollWindow-Y" 2637 + " maxScreenY=" + maxScreenY + " height=" + height 2638 + " mZoomScrollLimit=" + mZoomScrollLimit + " y=" + y); 2639 } 2640 y += maxScreenY * mLastScrollY / maxZoomY - mLastTouchY; 2641 y *= Math.max(maxZoomY / maxScreenY, mZoomScrollInvLimit); 2642 mZoomScrollY = Math.max(0, Math.min(maxZoomY, (int) y)); 2643 } 2644 if (oldX != mZoomScrollX || oldY != mZoomScrollY) { 2645 invalidate(); 2646 } 2647 if (LOGV_ENABLED) { 2648 Log.v(LOGTAG, "moveZoomScrollWindow" 2649 + " scrollTo=(" + mZoomScrollX + ", " + mZoomScrollY + ")" 2650 + " mLastTouch=(" + mLastTouchX + ", " + mLastTouchY + ")" 2651 + " maxZoom=(" + maxZoomX + ", " + maxZoomY + ")" 2652 + " last=("+mLastScrollX+", "+mLastScrollY+")" 2653 + " x=" + x + " y=" + y); 2654 } 2655 } 2656 2657 private void setZoomScrollIn() { 2658 mZoomScrollStart = System.currentTimeMillis(); 2659 } 2660 2661 private float mZoomScrollLimit; 2662 private float mZoomScrollInvLimit; 2663 private int mLastScrollX; 2664 private int mLastScrollY; 2665 private long mZoomScrollStart; 2666 private int mZoomScrollX; 2667 private int mZoomScrollY; 2668 private float mLastZoomScrollRawX = -1000.0f; 2669 private float mLastZoomScrollRawY = -1000.0f; 2670 // The zoomed scale varies from 1.0 to DEFAULT_MIN_ZOOM_SCALE == 0.25. 2671 // The zoom animation duration SCROLL_ZOOM_DURATION == 0.5. 2672 // Two pressures compete for gridding; a high frame rate (e.g. 20 fps) 2673 // and minimizing font cache allocations (fewer frames is better). 2674 // A SCROLL_ZOOM_GRID of 6 permits about 20 zoom levels over 0.5 seconds: 2675 // the inverse of: 1.0, 1.16, 1.33, 1.5, 1.67, 1.84, 2.0, etc. to 4.0 2676 private static final int SCROLL_ZOOM_GRID = 6; 2677 private static final int SCROLL_ZOOM_DURATION = 500; 2678 // Make it easier to get to the bottom of a document by reserving a 32 2679 // pixel buffer, for when the starting drag is a bit below the bottom of 2680 // the magnify frame. 2681 private static final int SCROLL_ZOOM_FINGER_BUFFER = 32; 2682 2683 // draw history 2684 private boolean mDrawHistory = false; 2685 private Picture mHistoryPicture = null; 2686 private int mHistoryWidth = 0; 2687 private int mHistoryHeight = 0; 2688 2689 // Only check the flag, can be called from WebCore thread 2690 boolean drawHistory() { 2691 return mDrawHistory; 2692 } 2693 2694 // Should only be called in UI thread 2695 void switchOutDrawHistory() { 2696 if (null == mWebViewCore) return; // CallbackProxy may trigger this 2697 if (mDrawHistory) { 2698 mDrawHistory = false; 2699 invalidate(); 2700 int oldScrollX = mScrollX; 2701 int oldScrollY = mScrollY; 2702 mScrollX = pinLocX(mScrollX); 2703 mScrollY = pinLocY(mScrollY); 2704 if (oldScrollX != mScrollX || oldScrollY != mScrollY) { 2705 mUserScroll = false; 2706 mWebViewCore.sendMessage(EventHub.SYNC_SCROLL, oldScrollX, 2707 oldScrollY); 2708 } 2709 sendOurVisibleRect(); 2710 } 2711 } 2712 2713 /** 2714 * Class representing the node which is focused. 2715 */ 2716 private class FocusNode { 2717 public FocusNode() { 2718 mBounds = new Rect(); 2719 } 2720 // Only to be called by JNI 2721 private void setAll(boolean isTextField, boolean isTextArea, boolean 2722 isPassword, boolean isAnchor, boolean isRtlText, int maxLength, 2723 int textSize, int boundsX, int boundsY, int boundsRight, int 2724 boundsBottom, int nodePointer, int framePointer, String text, 2725 String name, int rootTextGeneration) { 2726 mIsTextField = isTextField; 2727 mIsTextArea = isTextArea; 2728 mIsPassword = isPassword; 2729 mIsAnchor = isAnchor; 2730 mIsRtlText = isRtlText; 2731 2732 mMaxLength = maxLength; 2733 mTextSize = textSize; 2734 2735 mBounds.set(boundsX, boundsY, boundsRight, boundsBottom); 2736 2737 2738 mNodePointer = nodePointer; 2739 mFramePointer = framePointer; 2740 mText = text; 2741 mName = name; 2742 mRootTextGeneration = rootTextGeneration; 2743 } 2744 public boolean mIsTextField; 2745 public boolean mIsTextArea; 2746 public boolean mIsPassword; 2747 public boolean mIsAnchor; 2748 public boolean mIsRtlText; 2749 2750 public int mSelectionStart; 2751 public int mSelectionEnd; 2752 public int mMaxLength; 2753 public int mTextSize; 2754 2755 public Rect mBounds; 2756 2757 public int mNodePointer; 2758 public int mFramePointer; 2759 public String mText; 2760 public String mName; 2761 public int mRootTextGeneration; 2762 } 2763 2764 // Warning: ONLY use mFocusNode AFTER calling nativeUpdateFocusNode(), 2765 // and ONLY if it returns true; 2766 private FocusNode mFocusNode = new FocusNode(); 2767 2768 /** 2769 * Delete text from start to end in the focused textfield. If there is no 2770 * focus, or if start == end, silently fail. If start and end are out of 2771 * order, swap them. 2772 * @param start Beginning of selection to delete. 2773 * @param end End of selection to delete. 2774 */ 2775 /* package */ void deleteSelection(int start, int end) { 2776 mWebViewCore.sendMessage(EventHub.DELETE_SELECTION, start, end, 2777 new WebViewCore.FocusData(mFocusData)); 2778 } 2779 2780 /** 2781 * Set the selection to (start, end) in the focused textfield. If start and 2782 * end are out of order, swap them. 2783 * @param start Beginning of selection. 2784 * @param end End of selection. 2785 */ 2786 /* package */ void setSelection(int start, int end) { 2787 mWebViewCore.sendMessage(EventHub.SET_SELECTION, start, end, 2788 new WebViewCore.FocusData(mFocusData)); 2789 } 2790 2791 // Called by JNI when a touch event puts a textfield into focus. 2792 private void displaySoftKeyboard() { 2793 InputMethodManager imm = (InputMethodManager) 2794 getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 2795 imm.showSoftInput(mTextEntry, 0); 2796 } 2797 2798 // Used to register the global focus change listener one time to avoid 2799 // multiple references to WebView 2800 private boolean mGlobalFocusChangeListenerAdded; 2801 2802 private void updateTextEntry() { 2803 if (mTextEntry == null) { 2804 mTextEntry = new TextDialog(mContext, WebView.this); 2805 // Initialize our generation number. 2806 mTextGeneration = 0; 2807 } 2808 // If we do not have focus, do nothing until we gain focus. 2809 if (!hasFocus() && !mTextEntry.hasFocus() 2810 || (mTouchMode >= FIRST_SCROLL_ZOOM 2811 && mTouchMode <= LAST_SCROLL_ZOOM)) { 2812 mNeedsUpdateTextEntry = true; 2813 return; 2814 } 2815 boolean alreadyThere = inEditingMode(); 2816 if (0 == mNativeClass || !nativeUpdateFocusNode()) { 2817 if (alreadyThere) { 2818 mTextEntry.remove(); 2819 } 2820 return; 2821 } 2822 FocusNode node = mFocusNode; 2823 if (!node.mIsTextField && !node.mIsTextArea) { 2824 if (alreadyThere) { 2825 mTextEntry.remove(); 2826 } 2827 return; 2828 } 2829 mTextEntry.setTextSize(contentToView(node.mTextSize)); 2830 Rect visibleRect = sendOurVisibleRect(); 2831 // Note that sendOurVisibleRect calls viewToContent, so the coordinates 2832 // should be in content coordinates. 2833 if (!Rect.intersects(node.mBounds, visibleRect)) { 2834 if (alreadyThere) { 2835 mTextEntry.remove(); 2836 } 2837 // Node is not on screen, so do not bother. 2838 return; 2839 } 2840 int x = node.mBounds.left; 2841 int y = node.mBounds.top; 2842 int width = node.mBounds.width(); 2843 int height = node.mBounds.height(); 2844 if (alreadyThere && mTextEntry.isSameTextField(node.mNodePointer)) { 2845 // It is possible that we have the same textfield, but it has moved, 2846 // i.e. In the case of opening/closing the screen. 2847 // In that case, we need to set the dimensions, but not the other 2848 // aspects. 2849 // We also need to restore the selection, which gets wrecked by 2850 // calling setTextEntryRect. 2851 Spannable spannable = (Spannable) mTextEntry.getText(); 2852 int start = Selection.getSelectionStart(spannable); 2853 int end = Selection.getSelectionEnd(spannable); 2854 setTextEntryRect(x, y, width, height); 2855 // If the text has been changed by webkit, update it. However, if 2856 // there has been more UI text input, ignore it. We will receive 2857 // another update when that text is recognized. 2858 if (node.mText != null && !node.mText.equals(spannable.toString()) 2859 && node.mRootTextGeneration == mTextGeneration) { 2860 mTextEntry.setTextAndKeepSelection(node.mText); 2861 } else { 2862 Selection.setSelection(spannable, start, end); 2863 } 2864 } else { 2865 String text = node.mText; 2866 setTextEntryRect(x, y, width, height); 2867 mTextEntry.setGravity(node.mIsRtlText ? Gravity.RIGHT : 2868 Gravity.NO_GRAVITY); 2869 // this needs to be called before update adapter thread starts to 2870 // ensure the mTextEntry has the same node pointer 2871 mTextEntry.setNodePointer(node.mNodePointer); 2872 int maxLength = -1; 2873 if (node.mIsTextField) { 2874 maxLength = node.mMaxLength; 2875 if (mWebViewCore.getSettings().getSaveFormData() 2876 && node.mName != null) { 2877 HashMap data = new HashMap(); 2878 data.put("text", node.mText); 2879 Message update = mPrivateHandler.obtainMessage( 2880 UPDATE_TEXT_ENTRY_ADAPTER, node.mNodePointer, 0, 2881 data); 2882 UpdateTextEntryAdapter updater = new UpdateTextEntryAdapter( 2883 node.mName, getUrl(), update); 2884 Thread t = new Thread(updater); 2885 t.start(); 2886 } 2887 } 2888 mTextEntry.setMaxLength(maxLength); 2889 ArrayAdapter<String> adapter = null; 2890 mTextEntry.setAdapter(adapter); 2891 mTextEntry.setInPassword(node.mIsPassword); 2892 mTextEntry.setSingleLine(node.mIsTextField); 2893 if (null == text) { 2894 mTextEntry.setText("", 0, 0); 2895 } else { 2896 mTextEntry.setText(text, 0, text.length()); 2897 } 2898 mTextEntry.requestFocus(); 2899 } 2900 if (!mGlobalFocusChangeListenerAdded) { 2901 getViewTreeObserver().addOnGlobalFocusChangeListener(this); 2902 mGlobalFocusChangeListenerAdded = true; 2903 } 2904 } 2905 2906 private class UpdateTextEntryAdapter implements Runnable { 2907 private String mName; 2908 private String mUrl; 2909 private Message mUpdateMessage; 2910 2911 public UpdateTextEntryAdapter(String name, String url, Message msg) { 2912 mName = name; 2913 mUrl = url; 2914 mUpdateMessage = msg; 2915 } 2916 2917 public void run() { 2918 ArrayList<String> pastEntries = mDatabase.getFormData(mUrl, mName); 2919 if (pastEntries.size() > 0) { 2920 AutoCompleteAdapter adapter = new 2921 AutoCompleteAdapter(mContext, pastEntries); 2922 ((HashMap) mUpdateMessage.obj).put("adapter", adapter); 2923 mUpdateMessage.sendToTarget(); 2924 } 2925 } 2926 } 2927 2928 private void setTextEntryRect(int x, int y, int width, int height) { 2929 x = contentToView(x); 2930 y = contentToView(y); 2931 width = contentToView(width); 2932 height = contentToView(height); 2933 mTextEntry.setRect(x, y, width, height); 2934 } 2935 2936 // This is used to determine long press with the enter key, or 2937 // a center key. Does not affect long press with the trackball/touch. 2938 private boolean mGotEnterDown = false; 2939 2940 @Override 2941 public boolean onKeyDown(int keyCode, KeyEvent event) { 2942 if (LOGV_ENABLED) { 2943 Log.v(LOGTAG, "keyDown at " + System.currentTimeMillis() 2944 + ", " + event); 2945 } 2946 2947 if (mNativeClass == 0) { 2948 return false; 2949 } 2950 2951 // do this hack up front, so it always works, regardless of touch-mode 2952 if (AUTO_REDRAW_HACK && (keyCode == KeyEvent.KEYCODE_CALL)) { 2953 mAutoRedraw = !mAutoRedraw; 2954 if (mAutoRedraw) { 2955 invalidate(); 2956 } 2957 return true; 2958 } 2959 2960 // Bubble up the key event if 2961 // 1. it is a system key; or 2962 // 2. the host application wants to handle it; or 2963 // 3. webview is in scroll-zoom state; 2964 if (event.isSystem() 2965 || mCallbackProxy.uiOverrideKeyEvent(event) 2966 || (mTouchMode >= FIRST_SCROLL_ZOOM && mTouchMode <= LAST_SCROLL_ZOOM)) { 2967 return false; 2968 } 2969 2970 if (mShiftIsPressed == false && nativeFocusNodeWantsKeyEvents() == false 2971 && (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT 2972 || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT)) { 2973 mExtendSelection = false; 2974 mShiftIsPressed = true; 2975 if (nativeUpdateFocusNode()) { 2976 FocusNode node = mFocusNode; 2977 mSelectX = contentToView(node.mBounds.left); 2978 mSelectY = contentToView(node.mBounds.top); 2979 } else { 2980 mSelectX = mScrollX + (int) mLastTouchX; 2981 mSelectY = mScrollY + (int) mLastTouchY; 2982 } 2983 } 2984 2985 if (keyCode >= KeyEvent.KEYCODE_DPAD_UP 2986 && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) { 2987 // always handle the navigation keys in the UI thread 2988 switchOutDrawHistory(); 2989 if (navHandledKey(keyCode, 1, false, event.getEventTime())) { 2990 playSoundEffect(keyCodeToSoundsEffect(keyCode)); 2991 return true; 2992 } 2993 // Bubble up the key event as WebView doesn't handle it 2994 return false; 2995 } 2996 2997 if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER 2998 || keyCode == KeyEvent.KEYCODE_ENTER) { 2999 switchOutDrawHistory(); 3000 if (event.getRepeatCount() == 0) { 3001 mGotEnterDown = true; 3002 mPrivateHandler.sendMessageDelayed(mPrivateHandler 3003 .obtainMessage(LONG_PRESS_ENTER), LONG_PRESS_TIMEOUT); 3004 nativeRecordButtons(true, true); 3005 return true; 3006 } 3007 // Bubble up the key event as WebView doesn't handle it 3008 return false; 3009 } 3010 3011 if (getSettings().getNavDump()) { 3012 switch (keyCode) { 3013 case KeyEvent.KEYCODE_4: 3014 // "/data/data/com.android.browser/displayTree.txt" 3015 nativeDumpDisplayTree(getUrl()); 3016 break; 3017 case KeyEvent.KEYCODE_5: 3018 case KeyEvent.KEYCODE_6: 3019 // 5: dump the dom tree to the file 3020 // "/data/data/com.android.browser/domTree.txt" 3021 // 6: dump the dom tree to the adb log 3022 mWebViewCore.sendMessage(EventHub.DUMP_DOMTREE, 3023 (keyCode == KeyEvent.KEYCODE_5) ? 1 : 0, 0); 3024 break; 3025 case KeyEvent.KEYCODE_7: 3026 case KeyEvent.KEYCODE_8: 3027 // 7: dump the render tree to the file 3028 // "/data/data/com.android.browser/renderTree.txt" 3029 // 8: dump the render tree to the adb log 3030 mWebViewCore.sendMessage(EventHub.DUMP_RENDERTREE, 3031 (keyCode == KeyEvent.KEYCODE_7) ? 1 : 0, 0); 3032 break; 3033 case KeyEvent.KEYCODE_9: 3034 debugDump(); 3035 break; 3036 } 3037 } 3038 3039 // TODO: should we pass all the keys to DOM or check the meta tag 3040 if (nativeFocusNodeWantsKeyEvents() || true) { 3041 // pass the key to DOM 3042 mWebViewCore.sendMessage(EventHub.KEY_DOWN, event); 3043 // return true as DOM handles the key 3044 return true; 3045 } 3046 3047 // Bubble up the key event as WebView doesn't handle it 3048 return false; 3049 } 3050 3051 @Override 3052 public boolean onKeyUp(int keyCode, KeyEvent event) { 3053 if (LOGV_ENABLED) { 3054 Log.v(LOGTAG, "keyUp at " + System.currentTimeMillis() 3055 + ", " + event); 3056 } 3057 3058 if (mNativeClass == 0) { 3059 return false; 3060 } 3061 3062 // special CALL handling when focus node's href is "tel:XXX" 3063 if (keyCode == KeyEvent.KEYCODE_CALL && nativeUpdateFocusNode()) { 3064 FocusNode node = mFocusNode; 3065 String text = node.mText; 3066 if (!node.mIsTextField && !node.mIsTextArea && text != null 3067 && text.startsWith(SCHEME_TEL)) { 3068 Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(text)); 3069 getContext().startActivity(intent); 3070 return true; 3071 } 3072 } 3073 3074 // Bubble up the key event if 3075 // 1. it is a system key; or 3076 // 2. the host application wants to handle it; 3077 if (event.isSystem() || mCallbackProxy.uiOverrideKeyEvent(event)) { 3078 return false; 3079 } 3080 3081 // special handling in scroll_zoom state 3082 if (mTouchMode >= FIRST_SCROLL_ZOOM && mTouchMode <= LAST_SCROLL_ZOOM) { 3083 if (KeyEvent.KEYCODE_DPAD_CENTER == keyCode 3084 && mTouchMode != SCROLL_ZOOM_ANIMATION_IN) { 3085 setZoomScrollIn(); 3086 mTouchMode = SCROLL_ZOOM_ANIMATION_IN; 3087 invalidate(); 3088 return true; 3089 } 3090 return false; 3091 } 3092 3093 if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT 3094 || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { 3095 if (commitCopy()) { 3096 return true; 3097 } 3098 } 3099 3100 if (keyCode >= KeyEvent.KEYCODE_DPAD_UP 3101 && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) { 3102 // always handle the navigation keys in the UI thread 3103 // Bubble up the key event as WebView doesn't handle it 3104 return false; 3105 } 3106 3107 if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER 3108 || keyCode == KeyEvent.KEYCODE_ENTER) { 3109 // remove the long press message first 3110 mPrivateHandler.removeMessages(LONG_PRESS_ENTER); 3111 mGotEnterDown = false; 3112 3113 if (KeyEvent.KEYCODE_DPAD_CENTER == keyCode) { 3114 if (mShiftIsPressed) { 3115 return false; 3116 } 3117 if (getSettings().supportZoom()) { 3118 if (mTouchMode == TOUCH_DOUBLECLICK_MODE) { 3119 zoomScrollOut(); 3120 } else { 3121 if (LOGV_ENABLED) { 3122 Log.v(LOGTAG, "TOUCH_DOUBLECLICK_MODE"); 3123 } 3124 mPrivateHandler.sendMessageDelayed(mPrivateHandler 3125 .obtainMessage(SWITCH_TO_ENTER), TAP_TIMEOUT); 3126 mTouchMode = TOUCH_DOUBLECLICK_MODE; 3127 } 3128 return true; 3129 } 3130 } 3131 3132 Rect visibleRect = sendOurVisibleRect(); 3133 // Note that sendOurVisibleRect calls viewToContent, so the 3134 // coordinates should be in content coordinates. 3135 if (nativeUpdateFocusNode()) { 3136 if (Rect.intersects(mFocusNode.mBounds, visibleRect)) { 3137 nativeSetFollowedLink(true); 3138 mWebViewCore.sendMessage(EventHub.SET_FINAL_FOCUS, 3139 EventHub.BLOCK_FOCUS_CHANGE_UNTIL_KEY_UP, 0, 3140 new WebViewCore.FocusData(mFocusData)); 3141 playSoundEffect(SoundEffectConstants.CLICK); 3142 if (!mCallbackProxy.uiOverrideUrlLoading(mFocusNode.mText)) { 3143 // use CLICK instead of KEY_DOWN/KEY_UP so that we can 3144 // trigger mouse click events 3145 mWebViewCore.sendMessage(EventHub.CLICK); 3146 } 3147 } 3148 return true; 3149 } 3150 // Bubble up the key event as WebView doesn't handle it 3151 return false; 3152 } 3153 3154 // TODO: should we pass all the keys to DOM or check the meta tag 3155 if (nativeFocusNodeWantsKeyEvents() || true) { 3156 // pass the key to DOM 3157 mWebViewCore.sendMessage(EventHub.KEY_UP, event); 3158 // return true as DOM handles the key 3159 return true; 3160 } 3161 3162 // Bubble up the key event as WebView doesn't handle it 3163 return false; 3164 } 3165 3166 /** 3167 * @hide 3168 */ 3169 public void emulateShiftHeld() { 3170 mShiftIsPressed = true; 3171 } 3172 3173 private boolean commitCopy() { 3174 boolean copiedSomething = false; 3175 if (mExtendSelection) { 3176 // copy region so core operates on copy without touching orig. 3177 Region selection = new Region(nativeGetSelection()); 3178 if (selection.isEmpty() == false) { 3179 Toast.makeText(mContext 3180 , com.android.internal.R.string.text_copied 3181 , Toast.LENGTH_SHORT).show(); 3182 mWebViewCore.sendMessage(EventHub.GET_SELECTION, selection); 3183 copiedSomething = true; 3184 } 3185 } 3186 mShiftIsPressed = false; 3187 if (mTouchMode == TOUCH_SELECT_MODE) { 3188 mTouchMode = TOUCH_INIT_MODE; 3189 } 3190 return copiedSomething; 3191 } 3192 3193 // Set this as a hierarchy change listener so we can know when this view 3194 // is removed and still have access to our parent. 3195 @Override 3196 protected void onAttachedToWindow() { 3197 super.onAttachedToWindow(); 3198 ViewParent parent = getParent(); 3199 if (parent instanceof ViewGroup) { 3200 ViewGroup p = (ViewGroup) parent; 3201 p.setOnHierarchyChangeListener(this); 3202 } 3203 } 3204 3205 @Override 3206 protected void onDetachedFromWindow() { 3207 super.onDetachedFromWindow(); 3208 ViewParent parent = getParent(); 3209 if (parent instanceof ViewGroup) { 3210 ViewGroup p = (ViewGroup) parent; 3211 p.setOnHierarchyChangeListener(null); 3212 } 3213 } 3214 3215 // Implementation for OnHierarchyChangeListener 3216 public void onChildViewAdded(View parent, View child) {} 3217 3218 // When we are removed, remove this as a global focus change listener. 3219 public void onChildViewRemoved(View p, View child) { 3220 if (child == this) { 3221 p.getViewTreeObserver().removeOnGlobalFocusChangeListener(this); 3222 mGlobalFocusChangeListenerAdded = false; 3223 } 3224 } 3225 3226 // Use this to know when the textview has lost focus, and something other 3227 // than the webview has gained focus. Stop drawing the focus ring, remove 3228 // the TextView, and set a flag to put it back when we regain focus. 3229 public void onGlobalFocusChanged(View oldFocus, View newFocus) { 3230 if (oldFocus == mTextEntry && newFocus != this) { 3231 mDrawFocusRing = false; 3232 mTextEntry.updateCachedTextfield(); 3233 removeView(mTextEntry); 3234 mNeedsUpdateTextEntry = true; 3235 } 3236 } 3237 3238 // To avoid drawing the focus ring, and remove the TextView when our window 3239 // loses focus. 3240 @Override 3241 public void onWindowFocusChanged(boolean hasWindowFocus) { 3242 if (hasWindowFocus) { 3243 if (hasFocus()) { 3244 // If our window regained focus, and we have focus, then begin 3245 // drawing the focus ring, and restore the TextView if 3246 // necessary. 3247 mDrawFocusRing = true; 3248 if (mNeedsUpdateTextEntry) { 3249 updateTextEntry(); 3250 } 3251 } else { 3252 // If our window gained focus, but we do not have it, do not 3253 // draw the focus ring. 3254 mDrawFocusRing = false; 3255 } 3256 } else { 3257 // If our window has lost focus, stop drawing the focus ring, and 3258 // remove the TextView if displayed, and flag it to be added when 3259 // we regain focus. 3260 mDrawFocusRing = false; 3261 mGotKeyDown = false; 3262 mShiftIsPressed = false; 3263 if (inEditingMode()) { 3264 clearTextEntry(); 3265 mNeedsUpdateTextEntry = true; 3266 } 3267 } 3268 invalidate(); 3269 super.onWindowFocusChanged(hasWindowFocus); 3270 } 3271 3272 @Override 3273 protected void onFocusChanged(boolean focused, int direction, 3274 Rect previouslyFocusedRect) { 3275 if (LOGV_ENABLED) { 3276 Log.v(LOGTAG, "MT focusChanged " + focused + ", " + direction); 3277 } 3278 if (focused) { 3279 // When we regain focus, if we have window focus, resume drawing 3280 // the focus ring, and add the TextView if necessary. 3281 if (hasWindowFocus()) { 3282 mDrawFocusRing = true; 3283 if (mNeedsUpdateTextEntry) { 3284 updateTextEntry(); 3285 mNeedsUpdateTextEntry = false; 3286 } 3287 } 3288 } else { 3289 // When we lost focus, unless focus went to the TextView (which is 3290 // true if we are in editing mode), stop drawing the focus ring. 3291 if (!inEditingMode()) { 3292 mDrawFocusRing = false; 3293 } 3294 mGotKeyDown = false; 3295 } 3296 3297 super.onFocusChanged(focused, direction, previouslyFocusedRect); 3298 } 3299 3300 @Override 3301 protected void onSizeChanged(int w, int h, int ow, int oh) { 3302 super.onSizeChanged(w, h, ow, oh); 3303 3304 // we always force, in case our height changed, in which case we still 3305 // want to send the notification over to webkit 3306 setNewZoomScale(mActualScale, true); 3307 } 3308 3309 @Override 3310 protected void onScrollChanged(int l, int t, int oldl, int oldt) { 3311 super.onScrollChanged(l, t, oldl, oldt); 3312 sendOurVisibleRect(); 3313 } 3314 3315 3316 @Override 3317 public boolean dispatchKeyEvent(KeyEvent event) { 3318 boolean dispatch = true; 3319 3320 if (!inEditingMode()) { 3321 if (event.getAction() == KeyEvent.ACTION_DOWN) { 3322 mGotKeyDown = true; 3323 } else { 3324 if (!mGotKeyDown) { 3325 /* 3326 * We got a key up for which we were not the recipient of 3327 * the original key down. Don't give it to the view. 3328 */ 3329 dispatch = false; 3330 } 3331 mGotKeyDown = false; 3332 } 3333 } 3334 3335 if (dispatch) { 3336 return super.dispatchKeyEvent(event); 3337 } else { 3338 // We didn't dispatch, so let something else handle the key 3339 return false; 3340 } 3341 } 3342 3343 // Here are the snap align logic: 3344 // 1. If it starts nearly horizontally or vertically, snap align; 3345 // 2. If there is a dramitic direction change, let it go; 3346 // 3. If there is a same direction back and forth, lock it. 3347 3348 // adjustable parameters 3349 private static final float MAX_SLOPE_FOR_DIAG = 1.5f; 3350 private static final int MIN_LOCK_SNAP_REVERSE_DISTANCE = 3351 ViewConfiguration.getTouchSlop(); 3352 private static final int MIN_BREAK_SNAP_CROSS_DISTANCE = 80; 3353 3354 @Override 3355 public boolean onTouchEvent(MotionEvent ev) { 3356 if (mNativeClass == 0 || !isClickable() || !isLongClickable()) { 3357 return false; 3358 } 3359 3360 if (LOGV_ENABLED) { 3361 Log.v(LOGTAG, ev + " at " + ev.getEventTime() + " mTouchMode=" 3362 + mTouchMode); 3363 } 3364 3365 int action = ev.getAction(); 3366 float x = ev.getX(); 3367 float y = ev.getY(); 3368 long eventTime = ev.getEventTime(); 3369 3370 // Due to the touch screen edge effect, a touch closer to the edge 3371 // always snapped to the edge. As getViewWidth() can be different from 3372 // getWidth() due to the scrollbar, adjusting the point to match 3373 // getViewWidth(). Same applied to the height. 3374 if (x > getViewWidth() - 1) { 3375 x = getViewWidth() - 1; 3376 } 3377 if (y > getViewHeight() - 1) { 3378 y = getViewHeight() - 1; 3379 } 3380 3381 // pass the touch events from UI thread to WebCore thread 3382 if (mForwardTouchEvents && mTouchMode != SCROLL_ZOOM_OUT 3383 && mTouchMode != SCROLL_ZOOM_ANIMATION_IN 3384 && mTouchMode != SCROLL_ZOOM_ANIMATION_OUT 3385 && (action != MotionEvent.ACTION_MOVE || 3386 eventTime - mLastSentTouchTime > TOUCH_SENT_INTERVAL)) { 3387 WebViewCore.TouchEventData ted = new WebViewCore.TouchEventData(); 3388 ted.mAction = action; 3389 ted.mX = viewToContent((int) x + mScrollX); 3390 ted.mY = viewToContent((int) y + mScrollY);; 3391 mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); 3392 mLastSentTouchTime = eventTime; 3393 } 3394 3395 switch (action) { 3396 case MotionEvent.ACTION_DOWN: { 3397 if (mTouchMode == SCROLL_ZOOM_ANIMATION_IN 3398 || mTouchMode == SCROLL_ZOOM_ANIMATION_OUT) { 3399 // no interaction while animation is in progress 3400 break; 3401 } else if (mTouchMode == SCROLL_ZOOM_OUT) { 3402 mLastScrollX = mZoomScrollX; 3403 mLastScrollY = mZoomScrollY; 3404 // If two taps are close, ignore the first tap 3405 } else if (!mScroller.isFinished()) { 3406 mScroller.abortAnimation(); 3407 mTouchMode = TOUCH_DRAG_START_MODE; 3408 mPrivateHandler.removeMessages(RESUME_WEBCORE_UPDATE); 3409 } else if (mShiftIsPressed) { 3410 mSelectX = mScrollX + (int) x; 3411 mSelectY = mScrollY + (int) y; 3412 mTouchMode = TOUCH_SELECT_MODE; 3413 if (LOGV_ENABLED) { 3414 Log.v(LOGTAG, "select=" + mSelectX + "," + mSelectY); 3415 } 3416 nativeMoveSelection(viewToContent(mSelectX) 3417 , viewToContent(mSelectY), false); 3418 mTouchSelection = mExtendSelection = true; 3419 } else { 3420 mTouchMode = TOUCH_INIT_MODE; 3421 } 3422 if (mTouchMode == TOUCH_INIT_MODE) { 3423 mPrivateHandler.sendMessageDelayed(mPrivateHandler 3424 .obtainMessage(SWITCH_TO_SHORTPRESS), TAP_TIMEOUT); 3425 } 3426 // Remember where the motion event started 3427 mLastTouchX = x; 3428 mLastTouchY = y; 3429 mLastTouchTime = eventTime; 3430 mVelocityTracker = VelocityTracker.obtain(); 3431 mSnapScrollMode = SNAP_NONE; 3432 break; 3433 } 3434 case MotionEvent.ACTION_MOVE: { 3435 if (mTouchMode == TOUCH_DONE_MODE 3436 || mTouchMode == SCROLL_ZOOM_ANIMATION_IN 3437 || mTouchMode == SCROLL_ZOOM_ANIMATION_OUT) { 3438 // no dragging during scroll zoom animation 3439 break; 3440 } 3441 if (mTouchMode == SCROLL_ZOOM_OUT) { 3442 // while fully zoomed out, move the virtual window 3443 moveZoomScrollWindow(x, y); 3444 break; 3445 } 3446 mVelocityTracker.addMovement(ev); 3447 3448 int deltaX = (int) (mLastTouchX - x); 3449 int deltaY = (int) (mLastTouchY - y); 3450 3451 if (mTouchMode != TOUCH_DRAG_MODE) { 3452 if (mTouchMode == TOUCH_SELECT_MODE) { 3453 mSelectX = mScrollX + (int) x; 3454 mSelectY = mScrollY + (int) y; 3455 if (LOGV_ENABLED) { 3456 Log.v(LOGTAG, "xtend=" + mSelectX + "," + mSelectY); 3457 } 3458 nativeMoveSelection(viewToContent(mSelectX) 3459 , viewToContent(mSelectY), true); 3460 invalidate(); 3461 break; 3462 } 3463 if ((deltaX * deltaX + deltaY * deltaY) 3464 < TOUCH_SLOP_SQUARE) { 3465 break; 3466 } 3467 3468 if (mTouchMode == TOUCH_SHORTPRESS_MODE 3469 || mTouchMode == TOUCH_SHORTPRESS_START_MODE) { 3470 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); 3471 } else if (mTouchMode == TOUCH_INIT_MODE) { 3472 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); 3473 } 3474 3475 // if it starts nearly horizontal or vertical, enforce it 3476 int ax = Math.abs(deltaX); 3477 int ay = Math.abs(deltaY); 3478 if (ax > MAX_SLOPE_FOR_DIAG * ay) { 3479 mSnapScrollMode = SNAP_X; 3480 mSnapPositive = deltaX > 0; 3481 } else if (ay > MAX_SLOPE_FOR_DIAG * ax) { 3482 mSnapScrollMode = SNAP_Y; 3483 mSnapPositive = deltaY > 0; 3484 } 3485 3486 mTouchMode = TOUCH_DRAG_MODE; 3487 WebViewCore.pauseUpdate(mWebViewCore); 3488 int contentX = viewToContent((int) x + mScrollX); 3489 int contentY = viewToContent((int) y + mScrollY); 3490 if (inEditingMode()) { 3491 mTextEntry.updateCachedTextfield(); 3492 } 3493 nativeClearFocus(contentX, contentY); 3494 // remove the zoom anchor if there is any 3495 if (mZoomScale != 0) { 3496 mWebViewCore 3497 .sendMessage(EventHub.SET_SNAP_ANCHOR, 0, 0); 3498 } 3499 } 3500 3501 // do pan 3502 int newScrollX = pinLocX(mScrollX + deltaX); 3503 deltaX = newScrollX - mScrollX; 3504 int newScrollY = pinLocY(mScrollY + deltaY); 3505 deltaY = newScrollY - mScrollY; 3506 boolean done = false; 3507 if (deltaX == 0 && deltaY == 0) { 3508 done = true; 3509 } else { 3510 if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_Y) { 3511 int ax = Math.abs(deltaX); 3512 int ay = Math.abs(deltaY); 3513 if (mSnapScrollMode == SNAP_X) { 3514 // radical change means getting out of snap mode 3515 if (ay > MAX_SLOPE_FOR_DIAG * ax 3516 && ay > MIN_BREAK_SNAP_CROSS_DISTANCE) { 3517 mSnapScrollMode = SNAP_NONE; 3518 } 3519 // reverse direction means lock in the snap mode 3520 if ((ax > MAX_SLOPE_FOR_DIAG * ay) && 3521 ((mSnapPositive && 3522 deltaX < -MIN_LOCK_SNAP_REVERSE_DISTANCE) 3523 || (!mSnapPositive && 3524 deltaX > MIN_LOCK_SNAP_REVERSE_DISTANCE))) { 3525 mSnapScrollMode = SNAP_X_LOCK; 3526 } 3527 } else { 3528 // radical change means getting out of snap mode 3529 if ((ax > MAX_SLOPE_FOR_DIAG * ay) 3530 && ax > MIN_BREAK_SNAP_CROSS_DISTANCE) { 3531 mSnapScrollMode = SNAP_NONE; 3532 } 3533 // reverse direction means lock in the snap mode 3534 if ((ay > MAX_SLOPE_FOR_DIAG * ax) && 3535 ((mSnapPositive && 3536 deltaY < -MIN_LOCK_SNAP_REVERSE_DISTANCE) 3537 || (!mSnapPositive && 3538 deltaY > MIN_LOCK_SNAP_REVERSE_DISTANCE))) { 3539 mSnapScrollMode = SNAP_Y_LOCK; 3540 } 3541 } 3542 } 3543 3544 if (mSnapScrollMode == SNAP_X 3545 || mSnapScrollMode == SNAP_X_LOCK) { 3546 scrollBy(deltaX, 0); 3547 mLastTouchX = x; 3548 } else if (mSnapScrollMode == SNAP_Y 3549 || mSnapScrollMode == SNAP_Y_LOCK) { 3550 scrollBy(0, deltaY); 3551 mLastTouchY = y; 3552 } else { 3553 scrollBy(deltaX, deltaY); 3554 mLastTouchX = x; 3555 mLastTouchY = y; 3556 } 3557 mLastTouchTime = eventTime; 3558 mUserScroll = true; 3559 } 3560 3561 boolean showPlusMinus = mMinZoomScale < mMaxZoomScale; 3562 boolean showMagnify = canZoomScrollOut(); 3563 if (mZoomControls != null && (showPlusMinus || showMagnify)) { 3564 if (mZoomControls.getVisibility() == View.VISIBLE) { 3565 mPrivateHandler.removeCallbacks(mZoomControlRunnable); 3566 } else { 3567 mZoomControls.show(showPlusMinus, showMagnify); 3568 } 3569 mPrivateHandler.postDelayed(mZoomControlRunnable, 3570 ZOOM_CONTROLS_TIMEOUT); 3571 } 3572 if (done) { 3573 // return false to indicate that we can't pan out of the 3574 // view space 3575 return false; 3576 } 3577 break; 3578 } 3579 case MotionEvent.ACTION_UP: { 3580 switch (mTouchMode) { 3581 case TOUCH_INIT_MODE: // tap 3582 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); 3583 if (getSettings().supportZoom()) { 3584 mPrivateHandler.sendMessageDelayed(mPrivateHandler 3585 .obtainMessage(RELEASE_SINGLE_TAP), 3586 DOUBLE_TAP_TIMEOUT); 3587 } else { 3588 // do short press now 3589 mTouchMode = TOUCH_DONE_MODE; 3590 doShortPress(); 3591 } 3592 break; 3593 case TOUCH_SELECT_MODE: 3594 commitCopy(); 3595 mTouchSelection = mExtendSelection = false; 3596 break; 3597 case SCROLL_ZOOM_ANIMATION_IN: 3598 case SCROLL_ZOOM_ANIMATION_OUT: 3599 // no action during scroll animation 3600 break; 3601 case SCROLL_ZOOM_OUT: 3602 if (LOGV_ENABLED) { 3603 Log.v(LOGTAG, "ACTION_UP SCROLL_ZOOM_OUT" 3604 + " eventTime - mLastTouchTime=" 3605 + (eventTime - mLastTouchTime)); 3606 } 3607 // for now, always zoom back when the drag completes 3608 if (true || eventTime - mLastTouchTime < TAP_TIMEOUT) { 3609 // but if we tap, zoom in where we tap 3610 if (eventTime - mLastTouchTime < TAP_TIMEOUT) { 3611 zoomScrollTap(x, y); 3612 } 3613 // start zooming in back to the original view 3614 setZoomScrollIn(); 3615 mTouchMode = SCROLL_ZOOM_ANIMATION_IN; 3616 invalidate(); 3617 } 3618 break; 3619 case TOUCH_SHORTPRESS_START_MODE: 3620 case TOUCH_SHORTPRESS_MODE: { 3621 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); 3622 if (eventTime - mLastTouchTime < TAP_TIMEOUT 3623 && getSettings().supportZoom()) { 3624 // Note: window manager will not release ACTION_UP 3625 // until all the previous action events are 3626 // returned. If GC happens, it can cause 3627 // SWITCH_TO_SHORTPRESS message fired before 3628 // ACTION_UP sent even time stamp of ACTION_UP is 3629 // less than the tap time out. We need to treat this 3630 // as tap instead of short press. 3631 mTouchMode = TOUCH_INIT_MODE; 3632 mPrivateHandler.sendMessageDelayed(mPrivateHandler 3633 .obtainMessage(RELEASE_SINGLE_TAP), 3634 DOUBLE_TAP_TIMEOUT); 3635 } else { 3636 mTouchMode = TOUCH_DONE_MODE; 3637 doShortPress(); 3638 } 3639 break; 3640 } 3641 case TOUCH_DRAG_MODE: 3642 // if the user waits a while w/o moving before the 3643 // up, we don't want to do a fling 3644 if (eventTime - mLastTouchTime <= MIN_FLING_TIME) { 3645 mVelocityTracker.addMovement(ev); 3646 doFling(); 3647 break; 3648 } 3649 WebViewCore.resumeUpdate(mWebViewCore); 3650 break; 3651 case TOUCH_DRAG_START_MODE: 3652 case TOUCH_DONE_MODE: 3653 // do nothing 3654 break; 3655 } 3656 // we also use mVelocityTracker == null to tell us that we are 3657 // not "moving around", so we can take the slower/prettier 3658 // mode in the drawing code 3659 if (mVelocityTracker != null) { 3660 mVelocityTracker.recycle(); 3661 mVelocityTracker = null; 3662 } 3663 break; 3664 } 3665 case MotionEvent.ACTION_CANCEL: { 3666 // we also use mVelocityTracker == null to tell us that we are 3667 // not "moving around", so we can take the slower/prettier 3668 // mode in the drawing code 3669 if (mVelocityTracker != null) { 3670 mVelocityTracker.recycle(); 3671 mVelocityTracker = null; 3672 } 3673 if (mTouchMode == SCROLL_ZOOM_OUT || 3674 mTouchMode == SCROLL_ZOOM_ANIMATION_IN) { 3675 scrollTo(mZoomScrollX, mZoomScrollY); 3676 } else if (mTouchMode == TOUCH_DRAG_MODE) { 3677 WebViewCore.resumeUpdate(mWebViewCore); 3678 } 3679 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); 3680 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); 3681 mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP); 3682 mTouchMode = TOUCH_DONE_MODE; 3683 int contentX = viewToContent((int) mLastTouchX + mScrollX); 3684 int contentY = viewToContent((int) mLastTouchY + mScrollY); 3685 if (inEditingMode()) { 3686 mTextEntry.updateCachedTextfield(); 3687 } 3688 nativeClearFocus(contentX, contentY); 3689 break; 3690 } 3691 } 3692 return true; 3693 } 3694 3695 private long mTrackballFirstTime = 0; 3696 private long mTrackballLastTime = 0; 3697 private float mTrackballRemainsX = 0.0f; 3698 private float mTrackballRemainsY = 0.0f; 3699 private int mTrackballXMove = 0; 3700 private int mTrackballYMove = 0; 3701 private boolean mExtendSelection = false; 3702 private boolean mTouchSelection = false; 3703 private static final int TRACKBALL_KEY_TIMEOUT = 1000; 3704 private static final int TRACKBALL_TIMEOUT = 200; 3705 private static final int TRACKBALL_WAIT = 100; 3706 private static final int TRACKBALL_SCALE = 400; 3707 private static final int TRACKBALL_SCROLL_COUNT = 5; 3708 private static final int TRACKBALL_MULTIPLIER = 3; 3709 private static final int SELECT_CURSOR_OFFSET = 16; 3710 private int mSelectX = 0; 3711 private int mSelectY = 0; 3712 private boolean mShiftIsPressed = false; 3713 private boolean mTrackballDown = false; 3714 private long mTrackballUpTime = 0; 3715 private long mLastFocusTime = 0; 3716 private Rect mLastFocusBounds; 3717 3718 // Set by default; BrowserActivity clears to interpret trackball data 3719 // directly for movement. Currently, the framework only passes 3720 // arrow key events, not trackball events, from one child to the next 3721 private boolean mMapTrackballToArrowKeys = true; 3722 3723 public void setMapTrackballToArrowKeys(boolean setMap) { 3724 mMapTrackballToArrowKeys = setMap; 3725 } 3726 3727 void resetTrackballTime() { 3728 mTrackballLastTime = 0; 3729 } 3730 3731 @Override 3732 public boolean onTrackballEvent(MotionEvent ev) { 3733 long time = ev.getEventTime(); 3734 if ((ev.getMetaState() & KeyEvent.META_ALT_ON) != 0) { 3735 if (ev.getY() > 0) pageDown(true); 3736 if (ev.getY() < 0) pageUp(true); 3737 return true; 3738 } 3739 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 3740 mPrivateHandler.removeMessages(SWITCH_TO_ENTER); 3741 mTrackballDown = true; 3742 if (mNativeClass != 0) { 3743 nativeRecordButtons(true, true); 3744 } 3745 if (time - mLastFocusTime <= TRACKBALL_TIMEOUT 3746 && !mLastFocusBounds.equals(nativeGetFocusRingBounds())) { 3747 nativeSelectBestAt(mLastFocusBounds); 3748 } 3749 if (LOGV_ENABLED) { 3750 Log.v(LOGTAG, "onTrackballEvent down ev=" + ev 3751 + " time=" + time 3752 + " mLastFocusTime=" + mLastFocusTime); 3753 } 3754 return false; // let common code in onKeyDown at it 3755 } 3756 if (ev.getAction() == MotionEvent.ACTION_UP) { 3757 // LONG_PRESS_ENTER is set in common onKeyDown 3758 mPrivateHandler.removeMessages(LONG_PRESS_ENTER); 3759 mTrackballDown = false; 3760 mTrackballUpTime = time; 3761 if (mShiftIsPressed) { 3762 mExtendSelection = true; 3763 } 3764 if (LOGV_ENABLED) { 3765 Log.v(LOGTAG, "onTrackballEvent up ev=" + ev 3766 + " time=" + time 3767 ); 3768 } 3769 return false; // let common code in onKeyUp at it 3770 } 3771 if (mMapTrackballToArrowKeys && mShiftIsPressed == false) { 3772 if (LOGV_ENABLED) Log.v(LOGTAG, "onTrackballEvent gmail quit"); 3773 return false; 3774 } 3775 // no move if we're still waiting on SWITCH_TO_ENTER timeout 3776 if (mTouchMode == TOUCH_DOUBLECLICK_MODE) { 3777 if (LOGV_ENABLED) Log.v(LOGTAG, "onTrackballEvent 2 click quit"); 3778 return true; 3779 } 3780 if (mTrackballDown) { 3781 if (LOGV_ENABLED) Log.v(LOGTAG, "onTrackballEvent down quit"); 3782 return true; // discard move if trackball is down 3783 } 3784 if (time - mTrackballUpTime < TRACKBALL_TIMEOUT) { 3785 if (LOGV_ENABLED) Log.v(LOGTAG, "onTrackballEvent up timeout quit"); 3786 return true; 3787 } 3788 // TODO: alternatively we can do panning as touch does 3789 switchOutDrawHistory(); 3790 if (time - mTrackballLastTime > TRACKBALL_TIMEOUT) { 3791 if (LOGV_ENABLED) { 3792 Log.v(LOGTAG, "onTrackballEvent time=" 3793 + time + " last=" + mTrackballLastTime); 3794 } 3795 mTrackballFirstTime = time; 3796 mTrackballXMove = mTrackballYMove = 0; 3797 } 3798 mTrackballLastTime = time; 3799 if (LOGV_ENABLED) { 3800 Log.v(LOGTAG, "onTrackballEvent ev=" + ev + " time=" + time); 3801 } 3802 mTrackballRemainsX += ev.getX(); 3803 mTrackballRemainsY += ev.getY(); 3804 doTrackball(time); 3805 return true; 3806 } 3807 3808 void moveSelection(float xRate, float yRate) { 3809 if (mNativeClass == 0) 3810 return; 3811 int width = getViewWidth(); 3812 int height = getViewHeight(); 3813 mSelectX += scaleTrackballX(xRate, width); 3814 mSelectY += scaleTrackballY(yRate, height); 3815 int maxX = width + mScrollX; 3816 int maxY = height + mScrollY; 3817 mSelectX = Math.min(maxX, Math.max(mScrollX - SELECT_CURSOR_OFFSET 3818 , mSelectX)); 3819 mSelectY = Math.min(maxY, Math.max(mScrollY - SELECT_CURSOR_OFFSET 3820 , mSelectY)); 3821 if (LOGV_ENABLED) { 3822 Log.v(LOGTAG, "moveSelection" 3823 + " mSelectX=" + mSelectX 3824 + " mSelectY=" + mSelectY 3825 + " mScrollX=" + mScrollX 3826 + " mScrollY=" + mScrollY 3827 + " xRate=" + xRate 3828 + " yRate=" + yRate 3829 ); 3830 } 3831 nativeMoveSelection(viewToContent(mSelectX) 3832 , viewToContent(mSelectY), mExtendSelection); 3833 int scrollX = mSelectX < mScrollX ? -SELECT_CURSOR_OFFSET 3834 : mSelectX > maxX - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET 3835 : 0; 3836 int scrollY = mSelectY < mScrollY ? -SELECT_CURSOR_OFFSET 3837 : mSelectY > maxY - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET 3838 : 0; 3839 pinScrollBy(scrollX, scrollY, true); 3840 Rect select = new Rect(mSelectX, mSelectY, mSelectX + 1, mSelectY + 1); 3841 requestRectangleOnScreen(select); 3842 invalidate(); 3843 } 3844 3845 private int scaleTrackballX(float xRate, int width) { 3846 int xMove = (int) (xRate / TRACKBALL_SCALE * width); 3847 int nextXMove = xMove; 3848 if (xMove > 0) { 3849 if (xMove > mTrackballXMove) { 3850 xMove -= mTrackballXMove; 3851 } 3852 } else if (xMove < mTrackballXMove) { 3853 xMove -= mTrackballXMove; 3854 } 3855 mTrackballXMove = nextXMove; 3856 return xMove; 3857 } 3858 3859 private int scaleTrackballY(float yRate, int height) { 3860 int yMove = (int) (yRate / TRACKBALL_SCALE * height); 3861 int nextYMove = yMove; 3862 if (yMove > 0) { 3863 if (yMove > mTrackballYMove) { 3864 yMove -= mTrackballYMove; 3865 } 3866 } else if (yMove < mTrackballYMove) { 3867 yMove -= mTrackballYMove; 3868 } 3869 mTrackballYMove = nextYMove; 3870 return yMove; 3871 } 3872 3873 private int keyCodeToSoundsEffect(int keyCode) { 3874 switch(keyCode) { 3875 case KeyEvent.KEYCODE_DPAD_UP: 3876 return SoundEffectConstants.NAVIGATION_UP; 3877 case KeyEvent.KEYCODE_DPAD_RIGHT: 3878 return SoundEffectConstants.NAVIGATION_RIGHT; 3879 case KeyEvent.KEYCODE_DPAD_DOWN: 3880 return SoundEffectConstants.NAVIGATION_DOWN; 3881 case KeyEvent.KEYCODE_DPAD_LEFT: 3882 return SoundEffectConstants.NAVIGATION_LEFT; 3883 } 3884 throw new IllegalArgumentException("keyCode must be one of " + 3885 "{KEYCODE_DPAD_UP, KEYCODE_DPAD_RIGHT, KEYCODE_DPAD_DOWN, " + 3886 "KEYCODE_DPAD_LEFT}."); 3887 } 3888 3889 private void doTrackball(long time) { 3890 int elapsed = (int) (mTrackballLastTime - mTrackballFirstTime); 3891 if (elapsed == 0) { 3892 elapsed = TRACKBALL_TIMEOUT; 3893 } 3894 float xRate = mTrackballRemainsX * 1000 / elapsed; 3895 float yRate = mTrackballRemainsY * 1000 / elapsed; 3896 if (mShiftIsPressed) { 3897 moveSelection(xRate, yRate); 3898 mTrackballRemainsX = mTrackballRemainsY = 0; 3899 return; 3900 } 3901 float ax = Math.abs(xRate); 3902 float ay = Math.abs(yRate); 3903 float maxA = Math.max(ax, ay); 3904 if (LOGV_ENABLED) { 3905 Log.v(LOGTAG, "doTrackball elapsed=" + elapsed 3906 + " xRate=" + xRate 3907 + " yRate=" + yRate 3908 + " mTrackballRemainsX=" + mTrackballRemainsX 3909 + " mTrackballRemainsY=" + mTrackballRemainsY); 3910 } 3911 int width = mContentWidth - getViewWidth(); 3912 int height = mContentHeight - getViewHeight(); 3913 if (width < 0) width = 0; 3914 if (height < 0) height = 0; 3915 if (mTouchMode == SCROLL_ZOOM_OUT) { 3916 int oldX = mZoomScrollX; 3917 int oldY = mZoomScrollY; 3918 int maxWH = Math.max(width, height); 3919 mZoomScrollX += scaleTrackballX(xRate, maxWH); 3920 mZoomScrollY += scaleTrackballY(yRate, maxWH); 3921 if (LOGV_ENABLED) { 3922 Log.v(LOGTAG, "doTrackball SCROLL_ZOOM_OUT" 3923 + " mZoomScrollX=" + mZoomScrollX 3924 + " mZoomScrollY=" + mZoomScrollY); 3925 } 3926 mZoomScrollX = Math.min(width, Math.max(0, mZoomScrollX)); 3927 mZoomScrollY = Math.min(height, Math.max(0, mZoomScrollY)); 3928 if (oldX != mZoomScrollX || oldY != mZoomScrollY) { 3929 invalidate(); 3930 } 3931 mTrackballRemainsX = mTrackballRemainsY = 0; 3932 return; 3933 } 3934 ax = Math.abs(mTrackballRemainsX * TRACKBALL_MULTIPLIER); 3935 ay = Math.abs(mTrackballRemainsY * TRACKBALL_MULTIPLIER); 3936 maxA = Math.max(ax, ay); 3937 int count = Math.max(0, (int) maxA); 3938 int oldScrollX = mScrollX; 3939 int oldScrollY = mScrollY; 3940 if (count > 0) { 3941 int selectKeyCode = ax < ay ? mTrackballRemainsY < 0 ? 3942 KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_DPAD_DOWN : 3943 mTrackballRemainsX < 0 ? KeyEvent.KEYCODE_DPAD_LEFT : 3944 KeyEvent.KEYCODE_DPAD_RIGHT; 3945 if (LOGV_ENABLED) { 3946 Log.v(LOGTAG, "doTrackball keyCode=" + selectKeyCode 3947 + " count=" + count 3948 + " mTrackballRemainsX=" + mTrackballRemainsX 3949 + " mTrackballRemainsY=" + mTrackballRemainsY); 3950 } 3951 if (navHandledKey(selectKeyCode, count, false, time)) { 3952 playSoundEffect(keyCodeToSoundsEffect(selectKeyCode)); 3953 } 3954 mTrackballRemainsX = mTrackballRemainsY = 0; 3955 } 3956 if (count >= TRACKBALL_SCROLL_COUNT) { 3957 int xMove = scaleTrackballX(xRate, width); 3958 int yMove = scaleTrackballY(yRate, height); 3959 if (LOGV_ENABLED) { 3960 Log.v(LOGTAG, "doTrackball pinScrollBy" 3961 + " count=" + count 3962 + " xMove=" + xMove + " yMove=" + yMove 3963 + " mScrollX-oldScrollX=" + (mScrollX-oldScrollX) 3964 + " mScrollY-oldScrollY=" + (mScrollY-oldScrollY) 3965 ); 3966 } 3967 if (Math.abs(mScrollX - oldScrollX) > Math.abs(xMove)) { 3968 xMove = 0; 3969 } 3970 if (Math.abs(mScrollY - oldScrollY) > Math.abs(yMove)) { 3971 yMove = 0; 3972 } 3973 if (xMove != 0 || yMove != 0) { 3974 pinScrollBy(xMove, yMove, true); 3975 } 3976 mUserScroll = true; 3977 } 3978 mWebViewCore.sendMessage(EventHub.UNBLOCK_FOCUS); 3979 } 3980 3981 public void flingScroll(int vx, int vy) { 3982 int maxX = Math.max(computeHorizontalScrollRange() - getViewWidth(), 0); 3983 int maxY = Math.max(computeVerticalScrollRange() - getViewHeight(), 0); 3984 3985 mScroller.fling(mScrollX, mScrollY, vx, vy, 0, maxX, 0, maxY); 3986 invalidate(); 3987 } 3988 3989 private void doFling() { 3990 if (mVelocityTracker == null) { 3991 return; 3992 } 3993 int maxX = Math.max(computeHorizontalScrollRange() - getViewWidth(), 0); 3994 int maxY = Math.max(computeVerticalScrollRange() - getViewHeight(), 0); 3995 3996 mVelocityTracker.computeCurrentVelocity(1000); 3997 int vx = (int) mVelocityTracker.getXVelocity(); 3998 int vy = (int) mVelocityTracker.getYVelocity(); 3999 4000 if (mSnapScrollMode != SNAP_NONE) { 4001 if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_X_LOCK) { 4002 vy = 0; 4003 } else { 4004 vx = 0; 4005 } 4006 } 4007 4008 if (true /* EMG release: make our fling more like Maps' */) { 4009 // maps cuts their velocity in half 4010 vx = vx * 3 / 4; 4011 vy = vy * 3 / 4; 4012 } 4013 4014 mScroller.fling(mScrollX, mScrollY, -vx, -vy, 0, maxX, 0, maxY); 4015 // TODO: duration is calculated based on velocity, if the range is 4016 // small, the animation will stop before duration is up. We may 4017 // want to calculate how long the animation is going to run to precisely 4018 // resume the webcore update. 4019 final int time = mScroller.getDuration(); 4020 mPrivateHandler.sendEmptyMessageDelayed(RESUME_WEBCORE_UPDATE, time); 4021 invalidate(); 4022 } 4023 4024 private boolean zoomWithPreview(float scale) { 4025 float oldScale = mActualScale; 4026 4027 // snap to 100% if it is close 4028 if (scale > 0.95f && scale < 1.05f) { 4029 scale = 1.0f; 4030 } 4031 4032 setNewZoomScale(scale, false); 4033 4034 if (oldScale != mActualScale) { 4035 // use mZoomPickerScale to see zoom preview first 4036 mZoomStart = SystemClock.uptimeMillis(); 4037 mInvInitialZoomScale = 1.0f / oldScale; 4038 mInvFinalZoomScale = 1.0f / mActualScale; 4039 mZoomScale = mActualScale; 4040 invalidate(); 4041 return true; 4042 } else { 4043 return false; 4044 } 4045 } 4046 4047 /** 4048 * Returns a view containing zoom controls i.e. +/- buttons. The caller is 4049 * in charge of installing this view to the view hierarchy. This view will 4050 * become visible when the user starts scrolling via touch and fade away if 4051 * the user does not interact with it. 4052 */ 4053 public View getZoomControls() { 4054 if (!getSettings().supportZoom()) { 4055 Log.w(LOGTAG, "This WebView doesn't support zoom."); 4056 return null; 4057 } 4058 if (mZoomControls == null) { 4059 mZoomControls = createZoomControls(); 4060 4061 /* 4062 * need to be set to VISIBLE first so that getMeasuredHeight() in 4063 * {@link #onSizeChanged()} can return the measured value for proper 4064 * layout. 4065 */ 4066 mZoomControls.setVisibility(View.VISIBLE); 4067 mZoomControlRunnable = new Runnable() { 4068 public void run() { 4069 4070 /* Don't dismiss the controls if the user has 4071 * focus on them. Wait and check again later. 4072 */ 4073 if (!mZoomControls.hasFocus()) { 4074 mZoomControls.hide(); 4075 } else { 4076 mPrivateHandler.removeCallbacks(mZoomControlRunnable); 4077 mPrivateHandler.postDelayed(mZoomControlRunnable, 4078 ZOOM_CONTROLS_TIMEOUT); 4079 } 4080 } 4081 }; 4082 } 4083 return mZoomControls; 4084 } 4085 4086 /** 4087 * Perform zoom in in the webview 4088 * @return TRUE if zoom in succeeds. FALSE if no zoom changes. 4089 */ 4090 public boolean zoomIn() { 4091 // TODO: alternatively we can disallow this during draw history mode 4092 switchOutDrawHistory(); 4093 return zoomWithPreview(mActualScale * 1.25f); 4094 } 4095 4096 /** 4097 * Perform zoom out in the webview 4098 * @return TRUE if zoom out succeeds. FALSE if no zoom changes. 4099 */ 4100 public boolean zoomOut() { 4101 // TODO: alternatively we can disallow this during draw history mode 4102 switchOutDrawHistory(); 4103 return zoomWithPreview(mActualScale * 0.8f); 4104 } 4105 4106 private ExtendedZoomControls createZoomControls() { 4107 ExtendedZoomControls zoomControls = new ExtendedZoomControls(mContext 4108 , null); 4109 zoomControls.setOnZoomInClickListener(new OnClickListener() { 4110 public void onClick(View v) { 4111 // reset time out 4112 mPrivateHandler.removeCallbacks(mZoomControlRunnable); 4113 mPrivateHandler.postDelayed(mZoomControlRunnable, 4114 ZOOM_CONTROLS_TIMEOUT); 4115 zoomIn(); 4116 } 4117 }); 4118 zoomControls.setOnZoomOutClickListener(new OnClickListener() { 4119 public void onClick(View v) { 4120 // reset time out 4121 mPrivateHandler.removeCallbacks(mZoomControlRunnable); 4122 mPrivateHandler.postDelayed(mZoomControlRunnable, 4123 ZOOM_CONTROLS_TIMEOUT); 4124 zoomOut(); 4125 } 4126 }); 4127 zoomControls.setOnZoomMagnifyClickListener(new OnClickListener() { 4128 public void onClick(View v) { 4129 mPrivateHandler.removeCallbacks(mZoomControlRunnable); 4130 mPrivateHandler.postDelayed(mZoomControlRunnable, 4131 ZOOM_CONTROLS_TIMEOUT); 4132 zoomScrollOut(); 4133 } 4134 }); 4135 return zoomControls; 4136 } 4137 4138 private void updateSelection() { 4139 if (mNativeClass == 0) { 4140 return; 4141 } 4142 // mLastTouchX and mLastTouchY are the point in the current viewport 4143 int contentX = viewToContent((int) mLastTouchX + mScrollX); 4144 int contentY = viewToContent((int) mLastTouchY + mScrollY); 4145 int contentSize = ViewConfiguration.getTouchSlop(); 4146 Rect rect = new Rect(contentX - contentSize, contentY - contentSize, 4147 contentX + contentSize, contentY + contentSize); 4148 // If we were already focused on a textfield, update its cache. 4149 if (inEditingMode()) { 4150 mTextEntry.updateCachedTextfield(); 4151 } 4152 nativeSelectBestAt(rect); 4153 } 4154 4155 /*package*/ void shortPressOnTextField() { 4156 if (inEditingMode()) { 4157 View v = mTextEntry; 4158 int x = viewToContent((v.getLeft() + v.getRight()) >> 1); 4159 int y = viewToContent((v.getTop() + v.getBottom()) >> 1); 4160 int contentSize = ViewConfiguration.getTouchSlop(); 4161 nativeMotionUp(x, y, contentSize, true); 4162 } 4163 } 4164 4165 private void doShortPress() { 4166 if (mNativeClass == 0) { 4167 return; 4168 } 4169 switchOutDrawHistory(); 4170 // call uiOverride to check whether it is a special node, 4171 // phone/email/address, which are not handled by WebKit 4172 if (nativeUpdateFocusNode()) { 4173 FocusNode node = mFocusNode; 4174 if (!node.mIsTextField && !node.mIsTextArea) { 4175 if (mCallbackProxy.uiOverrideUrlLoading(node.mText)) { 4176 return; 4177 } 4178 } 4179 playSoundEffect(SoundEffectConstants.CLICK); 4180 } 4181 // mLastTouchX and mLastTouchY are the point in the current viewport 4182 int contentX = viewToContent((int) mLastTouchX + mScrollX); 4183 int contentY = viewToContent((int) mLastTouchY + mScrollY); 4184 int contentSize = ViewConfiguration.getTouchSlop(); 4185 nativeMotionUp(contentX, contentY, contentSize, true); 4186 } 4187 4188 @Override 4189 public boolean requestFocus(int direction, Rect previouslyFocusedRect) { 4190 boolean result = false; 4191 if (inEditingMode()) { 4192 result = mTextEntry.requestFocus(direction, previouslyFocusedRect); 4193 } else { 4194 result = super.requestFocus(direction, previouslyFocusedRect); 4195 if (mWebViewCore.getSettings().getNeedInitialFocus()) { 4196 // For cases such as GMail, where we gain focus from a direction, 4197 // we want to move to the first available link. 4198 // FIXME: If there are no visible links, we may not want to 4199 int fakeKeyDirection = 0; 4200 switch(direction) { 4201 case View.FOCUS_UP: 4202 fakeKeyDirection = KeyEvent.KEYCODE_DPAD_UP; 4203 break; 4204 case View.FOCUS_DOWN: 4205 fakeKeyDirection = KeyEvent.KEYCODE_DPAD_DOWN; 4206 break; 4207 case View.FOCUS_LEFT: 4208 fakeKeyDirection = KeyEvent.KEYCODE_DPAD_LEFT; 4209 break; 4210 case View.FOCUS_RIGHT: 4211 fakeKeyDirection = KeyEvent.KEYCODE_DPAD_RIGHT; 4212 break; 4213 default: 4214 return result; 4215 } 4216 if (mNativeClass != 0 && !nativeUpdateFocusNode()) { 4217 navHandledKey(fakeKeyDirection, 1, true, 0); 4218 } 4219 } 4220 } 4221 return result; 4222 } 4223 4224 @Override 4225 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 4226 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 4227 4228 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 4229 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 4230 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 4231 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 4232 4233 int measuredHeight = heightSize; 4234 int measuredWidth = widthSize; 4235 4236 // Grab the content size from WebViewCore. 4237 int contentHeight = mContentHeight; 4238 int contentWidth = mContentWidth; 4239 4240// Log.d(LOGTAG, "------- measure " + heightMode); 4241 4242 if (heightMode != MeasureSpec.EXACTLY) { 4243 mHeightCanMeasure = true; 4244 measuredHeight = contentHeight; 4245 if (heightMode == MeasureSpec.AT_MOST) { 4246 // If we are larger than the AT_MOST height, then our height can 4247 // no longer be measured and we should scroll internally. 4248 if (measuredHeight > heightSize) { 4249 measuredHeight = heightSize; 4250 mHeightCanMeasure = false; 4251 } 4252 } 4253 } else { 4254 mHeightCanMeasure = false; 4255 } 4256 if (mNativeClass != 0) { 4257 nativeSetHeightCanMeasure(mHeightCanMeasure); 4258 } 4259 // For the width, always use the given size unless unspecified. 4260 if (widthMode == MeasureSpec.UNSPECIFIED) { 4261 mWidthCanMeasure = true; 4262 measuredWidth = contentWidth; 4263 } else { 4264 mWidthCanMeasure = false; 4265 } 4266 4267 synchronized (this) { 4268 setMeasuredDimension(measuredWidth, measuredHeight); 4269 } 4270 } 4271 4272 @Override 4273 public boolean requestChildRectangleOnScreen(View child, 4274 Rect rect, 4275 boolean immediate) { 4276 rect.offset(child.getLeft() - child.getScrollX(), 4277 child.getTop() - child.getScrollY()); 4278 4279 int height = getHeight() - getHorizontalScrollbarHeight(); 4280 int screenTop = mScrollY; 4281 int screenBottom = screenTop + height; 4282 4283 int scrollYDelta = 0; 4284 4285 if (rect.bottom > screenBottom && rect.top > screenTop) { 4286 if (rect.height() > height) { 4287 scrollYDelta += (rect.top - screenTop); 4288 } else { 4289 scrollYDelta += (rect.bottom - screenBottom); 4290 } 4291 } else if (rect.top < screenTop) { 4292 scrollYDelta -= (screenTop - rect.top); 4293 } 4294 4295 int width = getWidth() - getVerticalScrollbarWidth(); 4296 int screenLeft = mScrollX; 4297 int screenRight = screenLeft + width; 4298 4299 int scrollXDelta = 0; 4300 4301 if (rect.right > screenRight && rect.left > screenLeft) { 4302 if (rect.width() > width) { 4303 scrollXDelta += (rect.left - screenLeft); 4304 } else { 4305 scrollXDelta += (rect.right - screenRight); 4306 } 4307 } else if (rect.left < screenLeft) { 4308 scrollXDelta -= (screenLeft - rect.left); 4309 } 4310 4311 if ((scrollYDelta | scrollXDelta) != 0) { 4312 return pinScrollBy(scrollXDelta, scrollYDelta, !immediate); 4313 } 4314 4315 return false; 4316 } 4317 4318 /* package */ void replaceTextfieldText(int oldStart, int oldEnd, 4319 String replace, int newStart, int newEnd) { 4320 HashMap arg = new HashMap(); 4321 arg.put("focusData", new WebViewCore.FocusData(mFocusData)); 4322 arg.put("replace", replace); 4323 arg.put("start", new Integer(newStart)); 4324 arg.put("end", new Integer(newEnd)); 4325 mWebViewCore.sendMessage(EventHub.REPLACE_TEXT, oldStart, oldEnd, arg); 4326 } 4327 4328 /* package */ void passToJavaScript(String currentText, KeyEvent event) { 4329 HashMap arg = new HashMap(); 4330 arg.put("focusData", new WebViewCore.FocusData(mFocusData)); 4331 arg.put("event", event); 4332 arg.put("currentText", currentText); 4333 // Increase our text generation number, and pass it to webcore thread 4334 mTextGeneration++; 4335 mWebViewCore.sendMessage(EventHub.PASS_TO_JS, mTextGeneration, 0, arg); 4336 // WebKit's document state is not saved until about to leave the page. 4337 // To make sure the host application, like Browser, has the up to date 4338 // document state when it goes to background, we force to save the 4339 // document state. 4340 mWebViewCore.removeMessages(EventHub.SAVE_DOCUMENT_STATE); 4341 mWebViewCore.sendMessageDelayed(EventHub.SAVE_DOCUMENT_STATE, 4342 new WebViewCore.FocusData(mFocusData), 1000); 4343 } 4344 4345 /* package */ WebViewCore getWebViewCore() { 4346 return mWebViewCore; 4347 } 4348 4349 //------------------------------------------------------------------------- 4350 // Methods can be called from a separate thread, like WebViewCore 4351 // If it needs to call the View system, it has to send message. 4352 //------------------------------------------------------------------------- 4353 4354 /** 4355 * General handler to receive message coming from webkit thread 4356 */ 4357 class PrivateHandler extends Handler { 4358 @Override 4359 public void handleMessage(Message msg) { 4360 switch (msg.what) { 4361 case REMEMBER_PASSWORD: { 4362 mDatabase.setUsernamePassword( 4363 msg.getData().getString("host"), 4364 msg.getData().getString("username"), 4365 msg.getData().getString("password")); 4366 ((Message) msg.obj).sendToTarget(); 4367 break; 4368 } 4369 case NEVER_REMEMBER_PASSWORD: { 4370 mDatabase.setUsernamePassword( 4371 msg.getData().getString("host"), null, null); 4372 ((Message) msg.obj).sendToTarget(); 4373 break; 4374 } 4375 case SWITCH_TO_SHORTPRESS: { 4376 if (mTouchMode == TOUCH_INIT_MODE) { 4377 mTouchMode = TOUCH_SHORTPRESS_START_MODE; 4378 updateSelection(); 4379 } 4380 break; 4381 } 4382 case SWITCH_TO_LONGPRESS: { 4383 mTouchMode = TOUCH_DONE_MODE; 4384 performLongClick(); 4385 updateTextEntry(); 4386 break; 4387 } 4388 case RELEASE_SINGLE_TAP: { 4389 mTouchMode = TOUCH_DONE_MODE; 4390 doShortPress(); 4391 break; 4392 } 4393 case SWITCH_TO_ENTER: 4394 if (LOGV_ENABLED) Log.v(LOGTAG, "SWITCH_TO_ENTER"); 4395 mTouchMode = TOUCH_DONE_MODE; 4396 onKeyUp(KeyEvent.KEYCODE_ENTER 4397 , new KeyEvent(KeyEvent.ACTION_UP 4398 , KeyEvent.KEYCODE_ENTER)); 4399 break; 4400 case SCROLL_BY_MSG_ID: 4401 setContentScrollBy(msg.arg1, msg.arg2); 4402 break; 4403 case SYNC_SCROLL_TO_MSG_ID: 4404 if (mUserScroll) { 4405 // if user has scrolled explicitly, don't sync the 4406 // scroll position any more 4407 mUserScroll = false; 4408 break; 4409 } 4410 // fall through 4411 case SCROLL_TO_MSG_ID: 4412 if (setContentScrollTo(msg.arg1, msg.arg2)) { 4413 // if we can't scroll to the exact position due to pin, 4414 // send a message to WebCore to re-scroll when we get a 4415 // new picture 4416 mUserScroll = false; 4417 mWebViewCore.sendMessage(EventHub.SYNC_SCROLL, 4418 msg.arg1, msg.arg2); 4419 } 4420 break; 4421 case SPAWN_SCROLL_TO_MSG_ID: 4422 spawnContentScrollTo(msg.arg1, msg.arg2); 4423 break; 4424 case NEW_PICTURE_MSG_ID: 4425 // called for new content 4426 final WebViewCore.DrawData draw = 4427 (WebViewCore.DrawData) msg.obj; 4428 final Point viewSize = draw.mViewPoint; 4429 if (mZoomScale > 0) { 4430 // use the same logic in sendViewSizeZoom() to make sure 4431 // the mZoomScale has matched the viewSize so that we 4432 // can clear mZoomScale 4433 if (Math.round(getViewWidth() / mZoomScale) == viewSize.x) { 4434 mZoomScale = 0; 4435 mWebViewCore.sendMessage(EventHub.SET_SNAP_ANCHOR, 4436 0, 0); 4437 } 4438 } 4439 // We update the layout (i.e. request a layout from the 4440 // view system) if the last view size that we sent to 4441 // WebCore matches the view size of the picture we just 4442 // received in the fixed dimension. 4443 final boolean updateLayout = viewSize.x == mLastWidthSent 4444 && viewSize.y == mLastHeightSent; 4445 recordNewContentSize(draw.mWidthHeight.x, 4446 draw.mWidthHeight.y, updateLayout); 4447 if (LOGV_ENABLED) { 4448 Rect b = draw.mInvalRegion.getBounds(); 4449 Log.v(LOGTAG, "NEW_PICTURE_MSG_ID {" + 4450 b.left+","+b.top+","+b.right+","+b.bottom+"}"); 4451 } 4452 invalidate(contentToView(draw.mInvalRegion.getBounds())); 4453 if (mPictureListener != null) { 4454 mPictureListener.onNewPicture(WebView.this, capturePicture()); 4455 } 4456 break; 4457 case WEBCORE_INITIALIZED_MSG_ID: 4458 // nativeCreate sets mNativeClass to a non-zero value 4459 nativeCreate(msg.arg1); 4460 break; 4461 case UPDATE_TEXTFIELD_TEXT_MSG_ID: 4462 // Make sure that the textfield is currently focused 4463 // and representing the same node as the pointer. 4464 if (inEditingMode() && 4465 mTextEntry.isSameTextField(msg.arg1)) { 4466 if (msg.getData().getBoolean("password")) { 4467 Spannable text = (Spannable) mTextEntry.getText(); 4468 int start = Selection.getSelectionStart(text); 4469 int end = Selection.getSelectionEnd(text); 4470 mTextEntry.setInPassword(true); 4471 // Restore the selection, which may have been 4472 // ruined by setInPassword. 4473 Spannable pword = (Spannable) mTextEntry.getText(); 4474 Selection.setSelection(pword, start, end); 4475 // If the text entry has created more events, ignore 4476 // this one. 4477 } else if (msg.arg2 == mTextGeneration) { 4478 mTextEntry.setTextAndKeepSelection( 4479 (String) msg.obj); 4480 } 4481 } 4482 break; 4483 case DID_FIRST_LAYOUT_MSG_ID: 4484 if (mNativeClass == 0) { 4485 break; 4486 } 4487// Do not reset the focus or clear the text; the user may have already 4488// navigated or entered text at this point. The focus should have gotten 4489// reset, if need be, when the focus cache was built. Similarly, the text 4490// view should already be torn down and rebuilt if needed. 4491// nativeResetFocus(); 4492// clearTextEntry(); 4493 HashMap scaleLimit = (HashMap) msg.obj; 4494 int minScale = (Integer) scaleLimit.get("minScale"); 4495 if (minScale == 0) { 4496 mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE; 4497 } else { 4498 mMinZoomScale = (float) (minScale / 100.0); 4499 } 4500 int maxScale = (Integer) scaleLimit.get("maxScale"); 4501 if (maxScale == 0) { 4502 mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE; 4503 } else { 4504 mMaxZoomScale = (float) (maxScale / 100.0); 4505 } 4506 // If history Picture is drawn, don't update zoomWidth 4507 if (mDrawHistory) { 4508 break; 4509 } 4510 int width = getViewWidth(); 4511 if (width == 0) { 4512 break; 4513 } 4514 int initialScale = msg.arg1; 4515 int viewportWidth = msg.arg2; 4516 // by default starting a new page with 100% zoom scale. 4517 float scale = 1.0f; 4518 if (mInitialScale > 0) { 4519 scale = mInitialScale / 100.0f; 4520 } else { 4521 if (mWebViewCore.getSettings().getUseWideViewPort()) { 4522 // force viewSizeChanged by setting mLastWidthSent 4523 // to 0 4524 mLastWidthSent = 0; 4525 } 4526 if (initialScale == 0) { 4527 // if viewportWidth is defined and it is smaller 4528 // than the view width, zoom in to fill the view 4529 if (viewportWidth > 0 && viewportWidth < width) { 4530 scale = (float) width / viewportWidth; 4531 } 4532 } else { 4533 scale = initialScale / 100.0f; 4534 } 4535 } 4536 setNewZoomScale(scale, false); 4537 break; 4538 case MARK_NODE_INVALID_ID: 4539 nativeMarkNodeInvalid(msg.arg1); 4540 break; 4541 case NOTIFY_FOCUS_SET_MSG_ID: 4542 if (mNativeClass != 0) { 4543 nativeNotifyFocusSet(inEditingMode()); 4544 } 4545 break; 4546 case UPDATE_TEXT_ENTRY_MSG_ID: 4547 updateTextEntry(); 4548 break; 4549 case RECOMPUTE_FOCUS_MSG_ID: 4550 if (mNativeClass != 0) { 4551 nativeRecomputeFocus(); 4552 } 4553 break; 4554 case UPDATE_TEXT_ENTRY_ADAPTER: 4555 HashMap data = (HashMap) msg.obj; 4556 if (mTextEntry.isSameTextField(msg.arg1)) { 4557 AutoCompleteAdapter adapter = 4558 (AutoCompleteAdapter) data.get("adapter"); 4559 mTextEntry.setAdapterCustom(adapter); 4560 } 4561 break; 4562 case UPDATE_CLIPBOARD: 4563 String str = (String) msg.obj; 4564 if (LOGV_ENABLED) { 4565 Log.v(LOGTAG, "UPDATE_CLIPBOARD " + str); 4566 } 4567 try { 4568 IClipboard clip = IClipboard.Stub.asInterface( 4569 ServiceManager.getService("clipboard")); 4570 clip.setClipboardText(str); 4571 } catch (android.os.RemoteException e) { 4572 Log.e(LOGTAG, "Clipboard failed", e); 4573 } 4574 break; 4575 case RESUME_WEBCORE_UPDATE: 4576 WebViewCore.resumeUpdate(mWebViewCore); 4577 break; 4578 4579 case LONG_PRESS_ENTER: 4580 // as this is shared by keydown and trackballdown, reset all 4581 // the states 4582 mGotEnterDown = false; 4583 mTrackballDown = false; 4584 // LONG_PRESS_ENTER is sent as a delayed message. If we 4585 // switch to windows overview, the WebView will be 4586 // temporarily removed from the view system. In that case, 4587 // do nothing. 4588 if (getParent() != null) { 4589 performLongClick(); 4590 } 4591 break; 4592 4593 case WEBCORE_NEED_TOUCH_EVENTS: 4594 mForwardTouchEvents = (msg.arg1 != 0); 4595 break; 4596 4597 case PREVENT_TOUCH_ID: 4598 // update may have already been paused by touch; restore since 4599 // this effectively aborts touch and skips logic in touch up 4600 if (mTouchMode == TOUCH_DRAG_MODE) { 4601 WebViewCore.resumeUpdate(mWebViewCore); 4602 } 4603 mTouchMode = TOUCH_DONE_MODE; 4604 break; 4605 4606 default: 4607 super.handleMessage(msg); 4608 break; 4609 } 4610 } 4611 } 4612 4613 // Class used to use a dropdown for a <select> element 4614 private class InvokeListBox implements Runnable { 4615 // Strings for the labels in the listbox. 4616 private String[] mArray; 4617 // Array representing whether each item is enabled. 4618 private boolean[] mEnableArray; 4619 // Whether the listbox allows multiple selection. 4620 private boolean mMultiple; 4621 // Passed in to a list with multiple selection to tell 4622 // which items are selected. 4623 private int[] mSelectedArray; 4624 // Passed in to a list with single selection to tell 4625 // where the initial selection is. 4626 private int mSelection; 4627 4628 private Container[] mContainers; 4629 4630 // Need these to provide stable ids to my ArrayAdapter, 4631 // which normally does not have stable ids. (Bug 1250098) 4632 private class Container extends Object { 4633 String mString; 4634 boolean mEnabled; 4635 int mId; 4636 4637 public String toString() { 4638 return mString; 4639 } 4640 } 4641 4642 /** 4643 * Subclass ArrayAdapter so we can disable OptionGroupLabels, 4644 * and allow filtering. 4645 */ 4646 private class MyArrayListAdapter extends ArrayAdapter<Container> { 4647 public MyArrayListAdapter(Context context, Container[] objects, boolean multiple) { 4648 super(context, 4649 multiple ? com.android.internal.R.layout.select_dialog_multichoice : 4650 com.android.internal.R.layout.select_dialog_singlechoice, 4651 objects); 4652 } 4653 4654 @Override 4655 public boolean hasStableIds() { 4656 return true; 4657 } 4658 4659 private Container item(int position) { 4660 if (position < 0 || position >= getCount()) { 4661 return null; 4662 } 4663 return (Container) getItem(position); 4664 } 4665 4666 @Override 4667 public long getItemId(int position) { 4668 Container item = item(position); 4669 if (item == null) { 4670 return -1; 4671 } 4672 return item.mId; 4673 } 4674 4675 @Override 4676 public boolean areAllItemsEnabled() { 4677 return false; 4678 } 4679 4680 @Override 4681 public boolean isEnabled(int position) { 4682 Container item = item(position); 4683 if (item == null) { 4684 return false; 4685 } 4686 return item.mEnabled; 4687 } 4688 } 4689 4690 private InvokeListBox(String[] array, 4691 boolean[] enabled, int[] selected) { 4692 mMultiple = true; 4693 mSelectedArray = selected; 4694 4695 int length = array.length; 4696 mContainers = new Container[length]; 4697 for (int i = 0; i < length; i++) { 4698 mContainers[i] = new Container(); 4699 mContainers[i].mString = array[i]; 4700 mContainers[i].mEnabled = enabled[i]; 4701 mContainers[i].mId = i; 4702 } 4703 } 4704 4705 private InvokeListBox(String[] array, boolean[] enabled, int 4706 selection) { 4707 mSelection = selection; 4708 mMultiple = false; 4709 4710 int length = array.length; 4711 mContainers = new Container[length]; 4712 for (int i = 0; i < length; i++) { 4713 mContainers[i] = new Container(); 4714 mContainers[i].mString = array[i]; 4715 mContainers[i].mEnabled = enabled[i]; 4716 mContainers[i].mId = i; 4717 } 4718 } 4719 4720 public void run() { 4721 final ListView listView = (ListView) LayoutInflater.from(mContext) 4722 .inflate(com.android.internal.R.layout.select_dialog, null); 4723 final MyArrayListAdapter adapter = new 4724 MyArrayListAdapter(mContext, mContainers, mMultiple); 4725 AlertDialog.Builder b = new AlertDialog.Builder(mContext) 4726 .setView(listView).setCancelable(true) 4727 .setInverseBackgroundForced(true); 4728 4729 if (mMultiple) { 4730 b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 4731 public void onClick(DialogInterface dialog, int which) { 4732 mWebViewCore.sendMessage( 4733 EventHub.LISTBOX_CHOICES, 4734 adapter.getCount(), 0, 4735 listView.getCheckedItemPositions()); 4736 }}); 4737 b.setNegativeButton(android.R.string.cancel, null); 4738 } 4739 final AlertDialog dialog = b.create(); 4740 listView.setAdapter(adapter); 4741 listView.setFocusableInTouchMode(true); 4742 // There is a bug (1250103) where the checks in a ListView with 4743 // multiple items selected are associated with the positions, not 4744 // the ids, so the items do not properly retain their checks when 4745 // filtered. Do not allow filtering on multiple lists until 4746 // that bug is fixed. 4747 4748 // Disable filter altogether 4749 // listView.setTextFilterEnabled(!mMultiple); 4750 if (mMultiple) { 4751 listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); 4752 int length = mSelectedArray.length; 4753 for (int i = 0; i < length; i++) { 4754 listView.setItemChecked(mSelectedArray[i], true); 4755 } 4756 } else { 4757 listView.setOnItemClickListener(new OnItemClickListener() { 4758 public void onItemClick(AdapterView parent, View v, 4759 int position, long id) { 4760 mWebViewCore.sendMessage( 4761 EventHub.SINGLE_LISTBOX_CHOICE, (int)id, 0); 4762 dialog.dismiss(); 4763 } 4764 }); 4765 if (mSelection != -1) { 4766 listView.setSelection(mSelection); 4767 } 4768 } 4769 dialog.show(); 4770 } 4771 } 4772 4773 /* 4774 * Request a dropdown menu for a listbox with multiple selection. 4775 * 4776 * @param array Labels for the listbox. 4777 * @param enabledArray Which positions are enabled. 4778 * @param selectedArray Which positions are initally selected. 4779 */ 4780 void requestListBox(String[] array, boolean[]enabledArray, int[] 4781 selectedArray) { 4782 mPrivateHandler.post( 4783 new InvokeListBox(array, enabledArray, selectedArray)); 4784 } 4785 4786 /* 4787 * Request a dropdown menu for a listbox with single selection or a single 4788 * <select> element. 4789 * 4790 * @param array Labels for the listbox. 4791 * @param enabledArray Which positions are enabled. 4792 * @param selection Which position is initally selected. 4793 */ 4794 void requestListBox(String[] array, boolean[]enabledArray, int selection) { 4795 mPrivateHandler.post( 4796 new InvokeListBox(array, enabledArray, selection)); 4797 } 4798 4799 // called by JNI 4800 private void sendFinalFocus(int frame, int node, int x, int y) { 4801 WebViewCore.FocusData focusData = new WebViewCore.FocusData(); 4802 focusData.mFrame = frame; 4803 focusData.mNode = node; 4804 focusData.mX = x; 4805 focusData.mY = y; 4806 mWebViewCore.sendMessage(EventHub.SET_FINAL_FOCUS, 4807 EventHub.NO_FOCUS_CHANGE_BLOCK, 0, focusData); 4808 } 4809 4810 // called by JNI 4811 private void setFocusData(int moveGeneration, int buildGeneration, 4812 int frame, int node, int x, int y, boolean ignoreNullFocus) { 4813 mFocusData.mMoveGeneration = moveGeneration; 4814 mFocusData.mBuildGeneration = buildGeneration; 4815 mFocusData.mFrame = frame; 4816 mFocusData.mNode = node; 4817 mFocusData.mX = x; 4818 mFocusData.mY = y; 4819 mFocusData.mIgnoreNullFocus = ignoreNullFocus; 4820 } 4821 4822 // called by JNI 4823 private void sendKitFocus() { 4824 WebViewCore.FocusData focusData = new WebViewCore.FocusData(mFocusData); 4825 mWebViewCore.sendMessage(EventHub.SET_KIT_FOCUS, focusData); 4826 } 4827 4828 // called by JNI 4829 private void sendMotionUp(int touchGeneration, int buildGeneration, 4830 int frame, int node, int x, int y, int size, boolean isClick, 4831 boolean retry) { 4832 WebViewCore.TouchUpData touchUpData = new WebViewCore.TouchUpData(); 4833 touchUpData.mMoveGeneration = touchGeneration; 4834 touchUpData.mBuildGeneration = buildGeneration; 4835 touchUpData.mSize = size; 4836 touchUpData.mIsClick = isClick; 4837 touchUpData.mRetry = retry; 4838 mFocusData.mFrame = touchUpData.mFrame = frame; 4839 mFocusData.mNode = touchUpData.mNode = node; 4840 mFocusData.mX = touchUpData.mX = x; 4841 mFocusData.mY = touchUpData.mY = y; 4842 mWebViewCore.sendMessage(EventHub.TOUCH_UP, touchUpData); 4843 } 4844 4845 4846 private int getScaledMaxXScroll() { 4847 int width; 4848 if (mHeightCanMeasure == false) { 4849 width = getViewWidth() / 4; 4850 } else { 4851 Rect visRect = new Rect(); 4852 calcOurVisibleRect(visRect); 4853 width = visRect.width() / 2; 4854 } 4855 // FIXME the divisor should be retrieved from somewhere 4856 return viewToContent(width); 4857 } 4858 4859 private int getScaledMaxYScroll() { 4860 int height; 4861 if (mHeightCanMeasure == false) { 4862 height = getViewHeight() / 4; 4863 } else { 4864 Rect visRect = new Rect(); 4865 calcOurVisibleRect(visRect); 4866 height = visRect.height() / 2; 4867 } 4868 // FIXME the divisor should be retrieved from somewhere 4869 // the closest thing today is hard-coded into ScrollView.java 4870 // (from ScrollView.java, line 363) int maxJump = height/2; 4871 return viewToContent(height); 4872 } 4873 4874 /** 4875 * Called by JNI to invalidate view 4876 */ 4877 private void viewInvalidate() { 4878 invalidate(); 4879 } 4880 4881 // return true if the key was handled 4882 private boolean navHandledKey(int keyCode, int count, boolean noScroll 4883 , long time) { 4884 if (mNativeClass == 0) { 4885 return false; 4886 } 4887 mLastFocusTime = time; 4888 mLastFocusBounds = nativeGetFocusRingBounds(); 4889 boolean keyHandled = nativeMoveFocus(keyCode, count, noScroll) == false; 4890 if (LOGV_ENABLED) { 4891 Log.v(LOGTAG, "navHandledKey mLastFocusBounds=" + mLastFocusBounds 4892 + " mLastFocusTime=" + mLastFocusTime 4893 + " handled=" + keyHandled); 4894 } 4895 if (keyHandled == false || mHeightCanMeasure == false) { 4896 return keyHandled; 4897 } 4898 Rect contentFocus = nativeGetFocusRingBounds(); 4899 if (contentFocus.isEmpty()) return keyHandled; 4900 Rect viewFocus = contentToView(contentFocus); 4901 Rect visRect = new Rect(); 4902 calcOurVisibleRect(visRect); 4903 Rect outset = new Rect(visRect); 4904 int maxXScroll = visRect.width() / 2; 4905 int maxYScroll = visRect.height() / 2; 4906 outset.inset(-maxXScroll, -maxYScroll); 4907 if (Rect.intersects(outset, viewFocus) == false) { 4908 return keyHandled; 4909 } 4910 // FIXME: Necessary because ScrollView/ListView do not scroll left/right 4911 int maxH = Math.min(viewFocus.right - visRect.right, maxXScroll); 4912 if (maxH > 0) { 4913 pinScrollBy(maxH, 0, true); 4914 } else { 4915 maxH = Math.max(viewFocus.left - visRect.left, -maxXScroll); 4916 if (maxH < 0) { 4917 pinScrollBy(maxH, 0, true); 4918 } 4919 } 4920 if (mLastFocusBounds.isEmpty()) return keyHandled; 4921 if (mLastFocusBounds.equals(contentFocus)) return keyHandled; 4922 if (LOGV_ENABLED) { 4923 Log.v(LOGTAG, "navHandledKey contentFocus=" + contentFocus); 4924 } 4925 requestRectangleOnScreen(viewFocus); 4926 mUserScroll = true; 4927 return keyHandled; 4928 } 4929 4930 /** 4931 * Set the background color. It's white by default. Pass 4932 * zero to make the view transparent. 4933 * @param color the ARGB color described by Color.java 4934 */ 4935 public void setBackgroundColor(int color) { 4936 mBackgroundColor = color; 4937 mWebViewCore.sendMessage(EventHub.SET_BACKGROUND_COLOR, color); 4938 } 4939 4940 public void debugDump() { 4941 nativeDebugDump(); 4942 mWebViewCore.sendMessage(EventHub.DUMP_NAVTREE); 4943 } 4944 4945 /** 4946 * Update our cache with updatedText. 4947 * @param updatedText The new text to put in our cache. 4948 */ 4949 /* package */ void updateCachedTextfield(String updatedText) { 4950 // Also place our generation number so that when we look at the cache 4951 // we recognize that it is up to date. 4952 nativeUpdateCachedTextfield(updatedText, mTextGeneration); 4953 } 4954 4955 // Never call this version except by updateCachedTextfield(String) - 4956 // we always want to pass in our generation number. 4957 private native void nativeUpdateCachedTextfield(String updatedText, 4958 int generation); 4959 private native void nativeClearFocus(int x, int y); 4960 private native void nativeCreate(int ptr); 4961 private native void nativeDebugDump(); 4962 private native void nativeDestroy(); 4963 private native void nativeDrawFocusRing(Canvas content); 4964 private native void nativeDrawSelection(Canvas content 4965 , int x, int y, boolean extendSelection); 4966 private native void nativeDrawSelectionRegion(Canvas content); 4967 private native boolean nativeUpdateFocusNode(); 4968 private native Rect nativeGetFocusRingBounds(); 4969 private native Rect nativeGetNavBounds(); 4970 private native void nativeMarkNodeInvalid(int node); 4971 private native void nativeMotionUp(int x, int y, int slop, boolean isClick); 4972 // returns false if it handled the key 4973 private native boolean nativeMoveFocus(int keyCode, int count, 4974 boolean noScroll); 4975 private native void nativeNotifyFocusSet(boolean inEditingMode); 4976 private native void nativeRecomputeFocus(); 4977 // Like many other of our native methods, you must make sure that 4978 // mNativeClass is not null before calling this method. 4979 private native void nativeRecordButtons(boolean pressed, 4980 boolean invalidate); 4981 private native void nativeResetFocus(); 4982 private native void nativeResetNavClipBounds(); 4983 private native void nativeSelectBestAt(Rect rect); 4984 private native void nativeSetFindIsDown(); 4985 private native void nativeSetFollowedLink(boolean followed); 4986 private native void nativeSetHeightCanMeasure(boolean measure); 4987 private native void nativeSetNavBounds(Rect rect); 4988 private native void nativeSetNavClipBounds(Rect rect); 4989 private native String nativeImageURI(int x, int y); 4990 /** 4991 * Returns true if the native focus nodes says it wants to handle key events 4992 * (ala plugins). This can only be called if mNativeClass is non-zero! 4993 */ 4994 private native boolean nativeFocusNodeWantsKeyEvents(); 4995 private native void nativeMoveSelection(int x, int y 4996 , boolean extendSelection); 4997 private native Region nativeGetSelection(); 4998 4999 private native void nativeDumpDisplayTree(String urlOrNull); 5000} 5001