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