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