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