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