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