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