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