OSDN Git Service

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