WebView.java revision 658ab7d787f64987d7c45aae08e5a12a073afe78
1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.webkit;
18
19import android.app.AlertDialog;
20import android.content.Context;
21import android.content.DialogInterface;
22import android.content.Intent;
23import android.content.DialogInterface.OnCancelListener;
24import android.database.DataSetObserver;
25import android.graphics.Bitmap;
26import android.graphics.Canvas;
27import android.graphics.Color;
28import android.graphics.Paint;
29import android.graphics.Path;
30import android.graphics.Picture;
31import android.graphics.Point;
32import android.graphics.Rect;
33import android.graphics.Region;
34import android.net.http.SslCertificate;
35import android.net.Uri;
36import android.os.Bundle;
37import android.os.Handler;
38import android.os.Message;
39import android.os.ServiceManager;
40import android.os.SystemClock;
41import android.provider.Checkin;
42import android.text.IClipboard;
43import android.text.Selection;
44import android.text.Spannable;
45import android.util.AttributeSet;
46import android.util.EventLog;
47import android.util.Log;
48import android.view.Gravity;
49import android.view.KeyEvent;
50import android.view.LayoutInflater;
51import android.view.MotionEvent;
52import android.view.SoundEffectConstants;
53import android.view.VelocityTracker;
54import android.view.View;
55import android.view.ViewConfiguration;
56import android.view.ViewGroup;
57import android.view.ViewParent;
58import android.view.ViewTreeObserver;
59import android.view.animation.AlphaAnimation;
60import android.view.inputmethod.InputMethodManager;
61import android.webkit.TextDialog.AutoCompleteAdapter;
62import android.webkit.WebViewCore.EventHub;
63import android.widget.AbsoluteLayout;
64import android.widget.Adapter;
65import android.widget.AdapterView;
66import android.widget.ArrayAdapter;
67import android.widget.FrameLayout;
68import android.widget.ImageView;
69import android.widget.ListView;
70import android.widget.Scroller;
71import android.widget.Toast;
72import android.widget.ZoomButtonsController;
73import android.widget.ZoomControls;
74import android.widget.AdapterView.OnItemClickListener;
75
76import java.io.File;
77import java.io.FileInputStream;
78import java.io.FileNotFoundException;
79import java.io.FileOutputStream;
80import java.io.IOException;
81import java.net.URLDecoder;
82import java.util.ArrayList;
83import java.util.HashMap;
84import java.util.List;
85
86/**
87 * <p>A View that displays web pages. This class is the basis upon which you
88 * can roll your own web browser or simply display some online content within your Activity.
89 * It uses the WebKit rendering engine to display
90 * web pages and includes methods to navigate forward and backward
91 * through a history, zoom in and out, perform text searches and more.</p>
92 * <p>To enable the built-in zoom, set
93 * {@link #getSettings() WebSettings}.{@link WebSettings#setBuiltInZoomControls(boolean)}
94 * (introduced in API version 3).
95 * <p>Note that, in order for your Activity to access the Internet and load web pages
96 * in a WebView, you must add the <var>INTERNET</var> permissions to your
97 * Android Manifest file:</p>
98 * <pre>&lt;uses-permission android:name="android.permission.INTERNET" /></pre>
99 *
100 * <p>This must be a child of the <code>&lt;manifest></code> element.</p>
101 *
102 * <h3>Basic usage</h3>
103 *
104 * <p>By default, a WebView provides no browser-like widgets, does not
105 * enable JavaScript and errors will be ignored. If your goal is only
106 * to display some HTML as a part of your UI, this is probably fine;
107 * the user won't need to interact with the web page beyond reading
108 * it, and the web page won't need to interact with the user. If you
109 * actually want a fully blown web browser, then you probably want to
110 * invoke the Browser application with your URL rather than show it
111 * with a WebView. See {@link android.content.Intent} for more information.</p>
112 *
113 * <pre class="prettyprint">
114 * WebView webview = new WebView(this);
115 * setContentView(webview);
116 *
117 * // Simplest usage: note that an exception will NOT be thrown
118 * // if there is an error loading this page (see below).
119 * webview.loadUrl("http://slashdot.org/");
120 *
121 * // Of course you can also load from any string:
122 * String summary = "&lt;html>&lt;body>You scored &lt;b>192</b> points.&lt;/body>&lt;/html>";
123 * webview.loadData(summary, "text/html", "utf-8");
124 * // ... although note that there are restrictions on what this HTML can do.
125 * // See the JavaDocs for loadData and loadDataWithBaseUrl for more info.
126 * </pre>
127 *
128 * <p>A WebView has several customization points where you can add your
129 * own behavior. These are:</p>
130 *
131 * <ul>
132 *   <li>Creating and setting a {@link android.webkit.WebChromeClient} subclass.
133 *       This class is called when something that might impact a
134 *       browser UI happens, for instance, progress updates and
135 *       JavaScript alerts are sent here.
136 *   </li>
137 *   <li>Creating and setting a {@link android.webkit.WebViewClient} subclass.
138 *       It will be called when things happen that impact the
139 *       rendering of the content, eg, errors or form submissions. You
140 *       can also intercept URL loading here.</li>
141 *   <li>Via the {@link android.webkit.WebSettings} class, which contains
142 *       miscellaneous configuration. </li>
143 *   <li>With the {@link android.webkit.WebView#addJavascriptInterface} method.
144 *       This lets you bind Java objects into the WebView so they can be
145 *       controlled from the web pages JavaScript.</li>
146 * </ul>
147 *
148 * <p>Here's a more complicated example, showing error handling,
149 *    settings, and progress notification:</p>
150 *
151 * <pre class="prettyprint">
152 * // Let's display the progress in the activity title bar, like the
153 * // browser app does.
154 * getWindow().requestFeature(Window.FEATURE_PROGRESS);
155 *
156 * webview.getSettings().setJavaScriptEnabled(true);
157 *
158 * final Activity activity = this;
159 * webview.setWebChromeClient(new WebChromeClient() {
160 *   public void onProgressChanged(WebView view, int progress) {
161 *     // Activities and WebViews measure progress with different scales.
162 *     // The progress meter will automatically disappear when we reach 100%
163 *     activity.setProgress(progress * 1000);
164 *   }
165 * });
166 * webview.setWebViewClient(new WebViewClient() {
167 *   public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
168 *     Toast.makeText(activity, "Oh no! " + description, Toast.LENGTH_SHORT).show();
169 *   }
170 * });
171 *
172 * webview.loadUrl("http://slashdot.org/");
173 * </pre>
174 *
175 * <h3>Cookie and window management</h3>
176 *
177 * <p>For obvious security reasons, your application has its own
178 * cache, cookie store etc - it does not share the Browser
179 * applications data. Cookies are managed on a separate thread, so
180 * operations like index building don't block the UI
181 * thread. Follow the instructions in {@link android.webkit.CookieSyncManager}
182 * if you want to use cookies in your application.
183 * </p>
184 *
185 * <p>By default, requests by the HTML to open new windows are
186 * ignored. This is true whether they be opened by JavaScript or by
187 * the target attribute on a link. You can customize your
188 * WebChromeClient to provide your own behaviour for opening multiple windows,
189 * and render them in whatever manner you want.</p>
190 *
191 * <p>Standard behavior for an Activity is to be destroyed and
192 * recreated when the devices orientation is changed. This will cause
193 * the WebView to reload the current page. If you don't want that, you
194 * can set your Activity to handle the orientation and keyboardHidden
195 * changes, and then just leave the WebView alone. It'll automatically
196 * re-orient itself as appropriate.</p>
197 */
198public class WebView extends AbsoluteLayout
199        implements ViewTreeObserver.OnGlobalFocusChangeListener,
200        ViewGroup.OnHierarchyChangeListener {
201
202    // if AUTO_REDRAW_HACK is true, then the CALL key will toggle redrawing
203    // the screen all-the-time. Good for profiling our drawing code
204    static private final boolean AUTO_REDRAW_HACK = false;
205    // true means redraw the screen all-the-time. Only with AUTO_REDRAW_HACK
206    private boolean mAutoRedraw;
207
208    // keep debugging parameters near the top of the file
209    static final String LOGTAG = "webview";
210    static final boolean DEBUG = false;
211    static final boolean LOGV_ENABLED = DEBUG;
212
213    private class ExtendedZoomControls extends FrameLayout {
214        public ExtendedZoomControls(Context context, AttributeSet attrs) {
215            super(context, attrs);
216            LayoutInflater inflater = (LayoutInflater)
217                    context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
218            inflater.inflate(com.android.internal.R.layout.zoom_magnify, this, true);
219            mZoomControls = (ZoomControls) findViewById(com.android.internal.R.id.zoomControls);
220            mZoomMagnify = (ImageView) findViewById(com.android.internal.R.id.zoomMagnify);
221        }
222
223        public void show(boolean showZoom, boolean canZoomOut) {
224            mZoomControls.setVisibility(showZoom ? View.VISIBLE : View.GONE);
225            mZoomMagnify.setVisibility(canZoomOut ? View.VISIBLE : View.GONE);
226            fade(View.VISIBLE, 0.0f, 1.0f);
227        }
228
229        public void hide() {
230            fade(View.GONE, 1.0f, 0.0f);
231        }
232
233        private void fade(int visibility, float startAlpha, float endAlpha) {
234            AlphaAnimation anim = new AlphaAnimation(startAlpha, endAlpha);
235            anim.setDuration(500);
236            startAnimation(anim);
237            setVisibility(visibility);
238        }
239
240        public void setIsZoomMagnifyEnabled(boolean isEnabled) {
241            mZoomMagnify.setEnabled(isEnabled);
242        }
243
244        public boolean hasFocus() {
245            return mZoomControls.hasFocus() || mZoomMagnify.hasFocus();
246        }
247
248        public void setOnZoomInClickListener(OnClickListener listener) {
249            mZoomControls.setOnZoomInClickListener(listener);
250        }
251
252        public void setOnZoomOutClickListener(OnClickListener listener) {
253            mZoomControls.setOnZoomOutClickListener(listener);
254        }
255
256        public void setOnZoomMagnifyClickListener(OnClickListener listener) {
257            mZoomMagnify.setOnClickListener(listener);
258        }
259
260        ZoomControls mZoomControls;
261        ImageView mZoomMagnify;
262    }
263
264    /**
265     *  Transportation object for returning WebView across thread boundaries.
266     */
267    public class WebViewTransport {
268        private WebView mWebview;
269
270        /**
271         * Set the WebView to the transportation object.
272         * @param webview The WebView to transport.
273         */
274        public synchronized void setWebView(WebView webview) {
275            mWebview = webview;
276        }
277
278        /**
279         * Return the WebView object.
280         * @return WebView The transported WebView object.
281         */
282        public synchronized WebView getWebView() {
283            return mWebview;
284        }
285    }
286
287    // A final CallbackProxy shared by WebViewCore and BrowserFrame.
288    private final CallbackProxy mCallbackProxy;
289
290    private final WebViewDatabase mDatabase;
291
292    // SSL certificate for the main top-level page (if secure)
293    private SslCertificate mCertificate;
294
295    // Native WebView pointer that is 0 until the native object has been
296    // created.
297    private int mNativeClass;
298    // This would be final but it needs to be set to null when the WebView is
299    // destroyed.
300    private WebViewCore mWebViewCore;
301    // Handler for dispatching UI messages.
302    /* package */ final Handler mPrivateHandler = new PrivateHandler();
303    private TextDialog mTextEntry;
304    // Used to ignore changes to webkit text that arrives to the UI side after
305    // more key events.
306    private int mTextGeneration;
307
308    // The list of loaded plugins.
309    private static PluginList sPluginList;
310
311    /**
312     * Position of the last touch event.
313     */
314    private float mLastTouchX;
315    private float mLastTouchY;
316
317    /**
318     * Time of the last touch event.
319     */
320    private long mLastTouchTime;
321
322    /**
323     * Time of the last time sending touch event to WebViewCore
324     */
325    private long mLastSentTouchTime;
326
327    /**
328     * The minimum elapsed time before sending another ACTION_MOVE event to
329     * WebViewCore
330     */
331    private static final int TOUCH_SENT_INTERVAL = 100;
332
333    /**
334     * Helper class to get velocity for fling
335     */
336    VelocityTracker mVelocityTracker;
337
338    /**
339     * Touch mode
340     */
341    private int mTouchMode = TOUCH_DONE_MODE;
342    private static final int TOUCH_INIT_MODE = 1;
343    private static final int TOUCH_DRAG_START_MODE = 2;
344    private static final int TOUCH_DRAG_MODE = 3;
345    private static final int TOUCH_SHORTPRESS_START_MODE = 4;
346    private static final int TOUCH_SHORTPRESS_MODE = 5;
347    private static final int TOUCH_DOUBLECLICK_MODE = 6;
348    private static final int TOUCH_DONE_MODE = 7;
349    private static final int TOUCH_SELECT_MODE = 8;
350    // touch mode values specific to scale+scroll
351    private static final int FIRST_SCROLL_ZOOM = 9;
352    private static final int SCROLL_ZOOM_ANIMATION_IN = 9;
353    private static final int SCROLL_ZOOM_ANIMATION_OUT = 10;
354    private static final int SCROLL_ZOOM_OUT = 11;
355    private static final int LAST_SCROLL_ZOOM = 11;
356    // end of touch mode values specific to scale+scroll
357
358    // Whether to forward the touch events to WebCore
359    private boolean mForwardTouchEvents = false;
360
361    // Whether to prevent drag during touch. The initial value depends on
362    // mForwardTouchEvents. If WebCore wants touch events, we assume it will
363    // take control of touch events unless it says no for touch down event.
364    private boolean mPreventDrag;
365
366    // If updateTextEntry gets called while we are out of focus, use this
367    // variable to remember to do it next time we gain focus.
368    private boolean mNeedsUpdateTextEntry = false;
369
370    // Whether or not to draw the focus ring.
371    private boolean mDrawFocusRing = true;
372
373    /**
374     * Customizable constant
375     */
376    // pre-computed square of ViewConfiguration.getScaledTouchSlop()
377    private int mTouchSlopSquare;
378    // pre-computed density adjusted navigation slop
379    private int mNavSlop;
380    // This should be ViewConfiguration.getTapTimeout()
381    // But system time out is 100ms, which is too short for the browser.
382    // In the browser, if it switches out of tap too soon, jump tap won't work.
383    private static final int TAP_TIMEOUT = 200;
384    // This should be ViewConfiguration.getLongPressTimeout()
385    // But system time out is 500ms, which is too short for the browser.
386    // With a short timeout, it's difficult to treat trigger a short press.
387    private static final int LONG_PRESS_TIMEOUT = 1000;
388    // needed to avoid flinging after a pause of no movement
389    private static final int MIN_FLING_TIME = 250;
390    // The time that the Zoom Controls are visible before fading away
391    private static final long ZOOM_CONTROLS_TIMEOUT =
392            ViewConfiguration.getZoomControlsTimeout();
393    // The amount of content to overlap between two screens when going through
394    // pages with the space bar, in pixels.
395    private static final int PAGE_SCROLL_OVERLAP = 24;
396
397    /**
398     * These prevent calling requestLayout if either dimension is fixed. This
399     * depends on the layout parameters and the measure specs.
400     */
401    boolean mWidthCanMeasure;
402    boolean mHeightCanMeasure;
403
404    // Remember the last dimensions we sent to the native side so we can avoid
405    // sending the same dimensions more than once.
406    int mLastWidthSent;
407    int mLastHeightSent;
408
409    private int mContentWidth;   // cache of value from WebViewCore
410    private int mContentHeight;  // cache of value from WebViewCore
411
412    // Need to have the separate control for horizontal and vertical scrollbar
413    // style than the View's single scrollbar style
414    private boolean mOverlayHorizontalScrollbar = true;
415    private boolean mOverlayVerticalScrollbar = false;
416
417    // our standard speed. this way small distances will be traversed in less
418    // time than large distances, but we cap the duration, so that very large
419    // distances won't take too long to get there.
420    private static final int STD_SPEED = 480;  // pixels per second
421    // time for the longest scroll animation
422    private static final int MAX_DURATION = 750;   // milliseconds
423    private Scroller mScroller;
424
425    private boolean mWrapContent;
426
427    // true if we should call webcore to draw the content, false means we have
428    // requested something but it isn't ready to draw yet.
429    private WebViewCore.FocusData mFocusData;
430    /**
431     * Private message ids
432     */
433    private static final int REMEMBER_PASSWORD = 1;
434    private static final int NEVER_REMEMBER_PASSWORD = 2;
435    private static final int SWITCH_TO_SHORTPRESS = 3;
436    private static final int SWITCH_TO_LONGPRESS = 4;
437    private static final int UPDATE_TEXT_ENTRY_ADAPTER = 6;
438    private static final int SWITCH_TO_ENTER = 7;
439    private static final int RESUME_WEBCORE_UPDATE = 8;
440
441    //! arg1=x, arg2=y
442    static final int SCROLL_TO_MSG_ID               = 10;
443    static final int SCROLL_BY_MSG_ID               = 11;
444    //! arg1=x, arg2=y
445    static final int SPAWN_SCROLL_TO_MSG_ID         = 12;
446    //! arg1=x, arg2=y
447    static final int SYNC_SCROLL_TO_MSG_ID          = 13;
448    static final int NEW_PICTURE_MSG_ID             = 14;
449    static final int UPDATE_TEXT_ENTRY_MSG_ID       = 15;
450    static final int WEBCORE_INITIALIZED_MSG_ID     = 16;
451    static final int UPDATE_TEXTFIELD_TEXT_MSG_ID   = 17;
452    static final int DID_FIRST_LAYOUT_MSG_ID        = 18;
453    static final int RECOMPUTE_FOCUS_MSG_ID         = 19;
454    static final int NOTIFY_FOCUS_SET_MSG_ID        = 20;
455    static final int MARK_NODE_INVALID_ID           = 21;
456    static final int UPDATE_CLIPBOARD               = 22;
457    static final int LONG_PRESS_ENTER               = 23;
458    static final int PREVENT_TOUCH_ID               = 24;
459    static final int WEBCORE_NEED_TOUCH_EVENTS      = 25;
460    // obj=Rect in doc coordinates
461    static final int INVAL_RECT_MSG_ID              = 26;
462
463    static final String[] HandlerDebugString = {
464        "REMEMBER_PASSWORD", // = 1;
465        "NEVER_REMEMBER_PASSWORD", // = 2;
466        "SWITCH_TO_SHORTPRESS", // = 3;
467        "SWITCH_TO_LONGPRESS", // = 4;
468        "5",
469        "UPDATE_TEXT_ENTRY_ADAPTER", // = 6;
470        "SWITCH_TO_ENTER", // = 7;
471        "RESUME_WEBCORE_UPDATE", // = 8;
472        "9",
473        "SCROLL_TO_MSG_ID", //               = 10;
474        "SCROLL_BY_MSG_ID", //               = 11;
475        "SPAWN_SCROLL_TO_MSG_ID", //         = 12;
476        "SYNC_SCROLL_TO_MSG_ID", //          = 13;
477        "NEW_PICTURE_MSG_ID", //             = 14;
478        "UPDATE_TEXT_ENTRY_MSG_ID", //       = 15;
479        "WEBCORE_INITIALIZED_MSG_ID", //     = 16;
480        "UPDATE_TEXTFIELD_TEXT_MSG_ID", //   = 17;
481        "DID_FIRST_LAYOUT_MSG_ID", //        = 18;
482        "RECOMPUTE_FOCUS_MSG_ID", //         = 19;
483        "NOTIFY_FOCUS_SET_MSG_ID", //        = 20;
484        "MARK_NODE_INVALID_ID", //           = 21;
485        "UPDATE_CLIPBOARD", //               = 22;
486        "LONG_PRESS_ENTER", //               = 23;
487        "PREVENT_TOUCH_ID", //               = 24;
488        "WEBCORE_NEED_TOUCH_EVENTS", //      = 25;
489        "INVAL_RECT_MSG_ID" //               = 26;
490    };
491
492    // width which view is considered to be fully zoomed out
493    static final int ZOOM_OUT_WIDTH = 1008;
494
495    private static final float DEFAULT_MAX_ZOOM_SCALE = 4.0f;
496    private static final float DEFAULT_MIN_ZOOM_SCALE = 0.25f;
497    // scale limit, which can be set through viewport meta tag in the web page
498    private float mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE;
499    private float mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE;
500    private boolean mMinZoomScaleFixed = false;
501
502    // initial scale in percent. 0 means using default.
503    private int mInitialScale = 0;
504
505    // set to true temporarily while the zoom control is being dragged
506    private boolean mPreviewZoomOnly = false;
507
508    // computed scale and inverse, from mZoomWidth.
509    private float mActualScale = 1;
510    private float mInvActualScale = 1;
511    // if this is non-zero, it is used on drawing rather than mActualScale
512    private float mZoomScale;
513    private float mInvInitialZoomScale;
514    private float mInvFinalZoomScale;
515    private long mZoomStart;
516    private static final int ZOOM_ANIMATION_LENGTH = 500;
517
518    private boolean mUserScroll = false;
519
520    private int mSnapScrollMode = SNAP_NONE;
521    private static final int SNAP_NONE = 1;
522    private static final int SNAP_X = 2;
523    private static final int SNAP_Y = 3;
524    private static final int SNAP_X_LOCK = 4;
525    private static final int SNAP_Y_LOCK = 5;
526    private boolean mSnapPositive;
527
528    // Used to match key downs and key ups
529    private boolean mGotKeyDown;
530
531    /* package */ static boolean mLogEvent = true;
532    private static final int EVENT_LOG_ZOOM_LEVEL_CHANGE = 70101;
533    private static final int EVENT_LOG_DOUBLE_TAP_DURATION = 70102;
534
535    // for event log
536    private long mLastTouchUpTime = 0;
537
538    /**
539     * URI scheme for telephone number
540     */
541    public static final String SCHEME_TEL = "tel:";
542    /**
543     * URI scheme for email address
544     */
545    public static final String SCHEME_MAILTO = "mailto:";
546    /**
547     * URI scheme for map address
548     */
549    public static final String SCHEME_GEO = "geo:0,0?q=";
550
551    private int mBackgroundColor = Color.WHITE;
552
553    // Used to notify listeners of a new picture.
554    private PictureListener mPictureListener;
555    /**
556     * Interface to listen for new pictures as they change.
557     */
558    public interface PictureListener {
559        /**
560         * Notify the listener that the picture has changed.
561         * @param view The WebView that owns the picture.
562         * @param picture The new picture.
563         */
564        public void onNewPicture(WebView view, Picture picture);
565    }
566
567    public class HitTestResult {
568        /**
569         * Default HitTestResult, where the target is unknown
570         */
571        public static final int UNKNOWN_TYPE = 0;
572        /**
573         * HitTestResult for hitting a HTML::a tag
574         */
575        public static final int ANCHOR_TYPE = 1;
576        /**
577         * HitTestResult for hitting a phone number
578         */
579        public static final int PHONE_TYPE = 2;
580        /**
581         * HitTestResult for hitting a map address
582         */
583        public static final int GEO_TYPE = 3;
584        /**
585         * HitTestResult for hitting an email address
586         */
587        public static final int EMAIL_TYPE = 4;
588        /**
589         * HitTestResult for hitting an HTML::img tag
590         */
591        public static final int IMAGE_TYPE = 5;
592        /**
593         * HitTestResult for hitting a HTML::a tag which contains HTML::img
594         */
595        public static final int IMAGE_ANCHOR_TYPE = 6;
596        /**
597         * HitTestResult for hitting a HTML::a tag with src=http
598         */
599        public static final int SRC_ANCHOR_TYPE = 7;
600        /**
601         * HitTestResult for hitting a HTML::a tag with src=http + HTML::img
602         */
603        public static final int SRC_IMAGE_ANCHOR_TYPE = 8;
604        /**
605         * HitTestResult for hitting an edit text area
606         */
607        public static final int EDIT_TEXT_TYPE = 9;
608
609        private int mType;
610        private String mExtra;
611
612        HitTestResult() {
613            mType = UNKNOWN_TYPE;
614        }
615
616        private void setType(int type) {
617            mType = type;
618        }
619
620        private void setExtra(String extra) {
621            mExtra = extra;
622        }
623
624        public int getType() {
625            return mType;
626        }
627
628        public String getExtra() {
629            return mExtra;
630        }
631    }
632
633    // The View containing the zoom controls
634    private ExtendedZoomControls mZoomControls;
635    private Runnable mZoomControlRunnable;
636
637    private ZoomButtonsController mZoomButtonsController;
638    private ImageView mZoomOverviewButton;
639    private ImageView mZoomFitPageButton;
640
641    // These keep track of the center point of the zoom.  They are used to
642    // determine the point around which we should zoom.
643    private float mZoomCenterX;
644    private float mZoomCenterY;
645
646    private ZoomButtonsController.OnZoomListener mZoomListener =
647            new ZoomButtonsController.OnZoomListener() {
648
649        public void onVisibilityChanged(boolean visible) {
650            if (visible) {
651                switchOutDrawHistory();
652                updateZoomButtonsEnabled();
653            }
654        }
655
656        public void onZoom(boolean zoomIn) {
657            if (zoomIn) {
658                zoomIn();
659            } else {
660                zoomOut();
661            }
662
663            updateZoomButtonsEnabled();
664        }
665    };
666
667    /**
668     * Construct a new WebView with a Context object.
669     * @param context A Context object used to access application assets.
670     */
671    public WebView(Context context) {
672        this(context, null);
673    }
674
675    /**
676     * Construct a new WebView with layout parameters.
677     * @param context A Context object used to access application assets.
678     * @param attrs An AttributeSet passed to our parent.
679     */
680    public WebView(Context context, AttributeSet attrs) {
681        this(context, attrs, com.android.internal.R.attr.webViewStyle);
682    }
683
684    /**
685     * Construct a new WebView with layout parameters and a default style.
686     * @param context A Context object used to access application assets.
687     * @param attrs An AttributeSet passed to our parent.
688     * @param defStyle The default style resource ID.
689     */
690    public WebView(Context context, AttributeSet attrs, int defStyle) {
691        super(context, attrs, defStyle);
692        init();
693
694        mCallbackProxy = new CallbackProxy(context, this);
695        mWebViewCore = new WebViewCore(context, this, mCallbackProxy);
696        mDatabase = WebViewDatabase.getInstance(context);
697        mFocusData = new WebViewCore.FocusData();
698        mFocusData.mFrame = 0;
699        mFocusData.mNode = 0;
700        mFocusData.mX = 0;
701        mFocusData.mY = 0;
702        mScroller = new Scroller(context);
703
704        initZoomController(context);
705    }
706
707    private void initZoomController(Context context) {
708        // Create the buttons controller
709        mZoomButtonsController = new ZoomButtonsController(this);
710        mZoomButtonsController.setOnZoomListener(mZoomListener);
711
712        // Create the accessory buttons
713        LayoutInflater inflater =
714                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
715        ViewGroup container = mZoomButtonsController.getContainer();
716        inflater.inflate(com.android.internal.R.layout.zoom_browser_accessory_buttons, container);
717        mZoomOverviewButton =
718                (ImageView) container.findViewById(com.android.internal.R.id.zoom_page_overview);
719        mZoomOverviewButton.setOnClickListener(
720            new View.OnClickListener() {
721                public void onClick(View v) {
722                    mZoomButtonsController.setVisible(false);
723                    zoomScrollOut();
724                    if (mLogEvent) {
725                        Checkin.updateStats(mContext.getContentResolver(),
726                                Checkin.Stats.Tag.BROWSER_ZOOM_OVERVIEW, 1, 0.0);
727                    }
728                }
729            });
730        mZoomFitPageButton =
731                (ImageView) container.findViewById(com.android.internal.R.id.zoom_fit_page);
732        mZoomFitPageButton.setOnClickListener(
733            new View.OnClickListener() {
734                public void onClick(View v) {
735                    zoomWithPreview(1f);
736                    updateZoomButtonsEnabled();
737                }
738            });
739    }
740
741    private void updateZoomButtonsEnabled() {
742        boolean canZoomIn = mActualScale < mMaxZoomScale;
743        boolean canZoomOut = mActualScale > mMinZoomScale;
744        if (!canZoomIn && !canZoomOut) {
745            // Hide the zoom in and out buttons, as well as the fit to page
746            // button, if the page cannot zoom
747            mZoomButtonsController.getZoomControls().setVisibility(View.GONE);
748            mZoomFitPageButton.setVisibility(View.GONE);
749        } else {
750            // Bring back the hidden zoom controls.
751            mZoomButtonsController.getZoomControls()
752                    .setVisibility(View.VISIBLE);
753            mZoomFitPageButton.setVisibility(View.VISIBLE);
754            // Set each one individually, as a page may be able to zoom in
755            // or out.
756            mZoomButtonsController.setZoomInEnabled(canZoomIn);
757            mZoomButtonsController.setZoomOutEnabled(canZoomOut);
758            mZoomFitPageButton.setEnabled(mActualScale != 1);
759        }
760        mZoomOverviewButton.setVisibility(canZoomScrollOut() ? View.VISIBLE:
761                View.GONE);
762    }
763
764    private void init() {
765        setWillNotDraw(false);
766        setFocusable(true);
767        setFocusableInTouchMode(true);
768        setClickable(true);
769        setLongClickable(true);
770
771        final int slop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
772        mTouchSlopSquare = slop * slop;
773        mMinLockSnapReverseDistance = slop;
774        // use one line height, 16 based on our current default font, for how
775        // far we allow a touch be away from the edge of a link
776        mNavSlop = (int) (16 * getContext().getResources()
777                .getDisplayMetrics().density);
778    }
779
780    /* package */ boolean onSavePassword(String schemePlusHost, String username,
781            String password, final Message resumeMsg) {
782       boolean rVal = false;
783       if (resumeMsg == null) {
784           // null resumeMsg implies saving password silently
785           mDatabase.setUsernamePassword(schemePlusHost, username, password);
786       } else {
787            final Message remember = mPrivateHandler.obtainMessage(
788                    REMEMBER_PASSWORD);
789            remember.getData().putString("host", schemePlusHost);
790            remember.getData().putString("username", username);
791            remember.getData().putString("password", password);
792            remember.obj = resumeMsg;
793
794            final Message neverRemember = mPrivateHandler.obtainMessage(
795                    NEVER_REMEMBER_PASSWORD);
796            neverRemember.getData().putString("host", schemePlusHost);
797            neverRemember.getData().putString("username", username);
798            neverRemember.getData().putString("password", password);
799            neverRemember.obj = resumeMsg;
800
801            new AlertDialog.Builder(getContext())
802                    .setTitle(com.android.internal.R.string.save_password_label)
803                    .setMessage(com.android.internal.R.string.save_password_message)
804                    .setPositiveButton(com.android.internal.R.string.save_password_notnow,
805                    new DialogInterface.OnClickListener() {
806                        public void onClick(DialogInterface dialog, int which) {
807                            resumeMsg.sendToTarget();
808                        }
809                    })
810                    .setNeutralButton(com.android.internal.R.string.save_password_remember,
811                    new DialogInterface.OnClickListener() {
812                        public void onClick(DialogInterface dialog, int which) {
813                            remember.sendToTarget();
814                        }
815                    })
816                    .setNegativeButton(com.android.internal.R.string.save_password_never,
817                    new DialogInterface.OnClickListener() {
818                        public void onClick(DialogInterface dialog, int which) {
819                            neverRemember.sendToTarget();
820                        }
821                    })
822                    .setOnCancelListener(new OnCancelListener() {
823                        public void onCancel(DialogInterface dialog) {
824                            resumeMsg.sendToTarget();
825                        }
826                    }).show();
827            // Return true so that WebViewCore will pause while the dialog is
828            // up.
829            rVal = true;
830        }
831       return rVal;
832    }
833
834    @Override
835    public void setScrollBarStyle(int style) {
836        if (style == View.SCROLLBARS_INSIDE_INSET
837                || style == View.SCROLLBARS_OUTSIDE_INSET) {
838            mOverlayHorizontalScrollbar = mOverlayVerticalScrollbar = false;
839        } else {
840            mOverlayHorizontalScrollbar = mOverlayVerticalScrollbar = true;
841        }
842        super.setScrollBarStyle(style);
843    }
844
845    /**
846     * Specify whether the horizontal scrollbar has overlay style.
847     * @param overlay TRUE if horizontal scrollbar should have overlay style.
848     */
849    public void setHorizontalScrollbarOverlay(boolean overlay) {
850        mOverlayHorizontalScrollbar = overlay;
851    }
852
853    /**
854     * Specify whether the vertical scrollbar has overlay style.
855     * @param overlay TRUE if vertical scrollbar should have overlay style.
856     */
857    public void setVerticalScrollbarOverlay(boolean overlay) {
858        mOverlayVerticalScrollbar = overlay;
859    }
860
861    /**
862     * Return whether horizontal scrollbar has overlay style
863     * @return TRUE if horizontal scrollbar has overlay style.
864     */
865    public boolean overlayHorizontalScrollbar() {
866        return mOverlayHorizontalScrollbar;
867    }
868
869    /**
870     * Return whether vertical scrollbar has overlay style
871     * @return TRUE if vertical scrollbar has overlay style.
872     */
873    public boolean overlayVerticalScrollbar() {
874        return mOverlayVerticalScrollbar;
875    }
876
877    /*
878     * Return the width of the view where the content of WebView should render
879     * to.
880     */
881    private int getViewWidth() {
882        if (!isVerticalScrollBarEnabled() || mOverlayVerticalScrollbar) {
883            return getWidth();
884        } else {
885            return getWidth() - getVerticalScrollbarWidth();
886        }
887    }
888
889    /*
890     * Return the height of the view where the content of WebView should render
891     * to.
892     */
893    private int getViewHeight() {
894        if (!isHorizontalScrollBarEnabled() || mOverlayHorizontalScrollbar) {
895            return getHeight();
896        } else {
897            return getHeight() - getHorizontalScrollbarHeight();
898        }
899    }
900
901    /**
902     * @return The SSL certificate for the main top-level page or null if
903     * there is no certificate (the site is not secure).
904     */
905    public SslCertificate getCertificate() {
906        return mCertificate;
907    }
908
909    /**
910     * Sets the SSL certificate for the main top-level page.
911     */
912    public void setCertificate(SslCertificate certificate) {
913        // here, the certificate can be null (if the site is not secure)
914        mCertificate = certificate;
915    }
916
917    //-------------------------------------------------------------------------
918    // Methods called by activity
919    //-------------------------------------------------------------------------
920
921    /**
922     * Save the username and password for a particular host in the WebView's
923     * internal database.
924     * @param host The host that required the credentials.
925     * @param username The username for the given host.
926     * @param password The password for the given host.
927     */
928    public void savePassword(String host, String username, String password) {
929        mDatabase.setUsernamePassword(host, username, password);
930    }
931
932    /**
933     * Set the HTTP authentication credentials for a given host and realm.
934     *
935     * @param host The host for the credentials.
936     * @param realm The realm for the credentials.
937     * @param username The username for the password. If it is null, it means
938     *                 password can't be saved.
939     * @param password The password
940     */
941    public void setHttpAuthUsernamePassword(String host, String realm,
942            String username, String password) {
943        mDatabase.setHttpAuthUsernamePassword(host, realm, username, password);
944    }
945
946    /**
947     * Retrieve the HTTP authentication username and password for a given
948     * host & realm pair
949     *
950     * @param host The host for which the credentials apply.
951     * @param realm The realm for which the credentials apply.
952     * @return String[] if found, String[0] is username, which can be null and
953     *         String[1] is password. Return null if it can't find anything.
954     */
955    public String[] getHttpAuthUsernamePassword(String host, String realm) {
956        return mDatabase.getHttpAuthUsernamePassword(host, realm);
957    }
958
959    /**
960     * Destroy the internal state of the WebView. This method should be called
961     * after the WebView has been removed from the view system. No other
962     * methods may be called on a WebView after destroy.
963     */
964    public void destroy() {
965        clearTextEntry();
966        if (mWebViewCore != null) {
967            // Set the handlers to null before destroying WebViewCore so no
968            // more messages will be posted.
969            mCallbackProxy.setWebViewClient(null);
970            mCallbackProxy.setWebChromeClient(null);
971            // Tell WebViewCore to destroy itself
972            WebViewCore webViewCore = mWebViewCore;
973            mWebViewCore = null; // prevent using partial webViewCore
974            webViewCore.destroy();
975            // Remove any pending messages that might not be serviced yet.
976            mPrivateHandler.removeCallbacksAndMessages(null);
977            mCallbackProxy.removeCallbacksAndMessages(null);
978            // Wake up the WebCore thread just in case it is waiting for a
979            // javascript dialog.
980            synchronized (mCallbackProxy) {
981                mCallbackProxy.notify();
982            }
983        }
984        if (mNativeClass != 0) {
985            nativeDestroy();
986            mNativeClass = 0;
987        }
988    }
989
990    /**
991     * Enables platform notifications of data state and proxy changes.
992     */
993    public static void enablePlatformNotifications() {
994        Network.enablePlatformNotifications();
995    }
996
997    /**
998     * If platform notifications are enabled, this should be called
999     * from onPause() or onStop().
1000     */
1001    public static void disablePlatformNotifications() {
1002        Network.disablePlatformNotifications();
1003    }
1004
1005    /**
1006     * Inform WebView of the network state. This is used to set
1007     * the javascript property window.navigator.isOnline and
1008     * generates the online/offline event as specified in HTML5, sec. 5.7.7
1009     * @param networkUp boolean indicating if network is available
1010     */
1011    public void setNetworkAvailable(boolean networkUp) {
1012        mWebViewCore.sendMessage(EventHub.SET_NETWORK_STATE,
1013                networkUp ? 1 : 0, 0);
1014    }
1015
1016    /**
1017     * Save the state of this WebView used in
1018     * {@link android.app.Activity#onSaveInstanceState}. Please note that this
1019     * method no longer stores the display data for this WebView. The previous
1020     * behavior could potentially leak files if {@link #restoreState} was never
1021     * called. See {@link #savePicture} and {@link #restorePicture} for saving
1022     * and restoring the display data.
1023     * @param outState The Bundle to store the WebView state.
1024     * @return The same copy of the back/forward list used to save the state. If
1025     *         saveState fails, the returned list will be null.
1026     * @see #savePicture
1027     * @see #restorePicture
1028     */
1029    public WebBackForwardList saveState(Bundle outState) {
1030        if (outState == null) {
1031            return null;
1032        }
1033        // We grab a copy of the back/forward list because a client of WebView
1034        // may have invalidated the history list by calling clearHistory.
1035        WebBackForwardList list = copyBackForwardList();
1036        final int currentIndex = list.getCurrentIndex();
1037        final int size = list.getSize();
1038        // We should fail saving the state if the list is empty or the index is
1039        // not in a valid range.
1040        if (currentIndex < 0 || currentIndex >= size || size == 0) {
1041            return null;
1042        }
1043        outState.putInt("index", currentIndex);
1044        // FIXME: This should just be a byte[][] instead of ArrayList but
1045        // Parcel.java does not have the code to handle multi-dimensional
1046        // arrays.
1047        ArrayList<byte[]> history = new ArrayList<byte[]>(size);
1048        for (int i = 0; i < size; i++) {
1049            WebHistoryItem item = list.getItemAtIndex(i);
1050            byte[] data = item.getFlattenedData();
1051            if (data == null) {
1052                // It would be very odd to not have any data for a given history
1053                // item. And we will fail to rebuild the history list without
1054                // flattened data.
1055                return null;
1056            }
1057            history.add(data);
1058        }
1059        outState.putSerializable("history", history);
1060        if (mCertificate != null) {
1061            outState.putBundle("certificate",
1062                               SslCertificate.saveState(mCertificate));
1063        }
1064        return list;
1065    }
1066
1067    /**
1068     * Save the current display data to the Bundle given. Used in conjunction
1069     * with {@link #saveState}.
1070     * @param b A Bundle to store the display data.
1071     * @param dest The file to store the serialized picture data. Will be
1072     *             overwritten with this WebView's picture data.
1073     * @return True if the picture was successfully saved.
1074     */
1075    public boolean savePicture(Bundle b, File dest) {
1076        if (dest == null || b == null) {
1077            return false;
1078        }
1079        final Picture p = capturePicture();
1080        try {
1081            final FileOutputStream out = new FileOutputStream(dest);
1082            p.writeToStream(out);
1083            out.close();
1084        } catch (FileNotFoundException e){
1085            e.printStackTrace();
1086        } catch (IOException e) {
1087            e.printStackTrace();
1088        } catch (RuntimeException e) {
1089            e.printStackTrace();
1090        }
1091        if (dest.length() > 0) {
1092            b.putInt("scrollX", mScrollX);
1093            b.putInt("scrollY", mScrollY);
1094            b.putFloat("scale", mActualScale);
1095            return true;
1096        }
1097        return false;
1098    }
1099
1100    /**
1101     * Restore the display data that was save in {@link #savePicture}. Used in
1102     * conjunction with {@link #restoreState}.
1103     * @param b A Bundle containing the saved display data.
1104     * @param src The file where the picture data was stored.
1105     * @return True if the picture was successfully restored.
1106     */
1107    public boolean restorePicture(Bundle b, File src) {
1108        if (src == null || b == null) {
1109            return false;
1110        }
1111        if (src.exists()) {
1112            Picture p = null;
1113            try {
1114                final FileInputStream in = new FileInputStream(src);
1115                p = Picture.createFromStream(in);
1116                in.close();
1117            } catch (FileNotFoundException e){
1118                e.printStackTrace();
1119            } catch (RuntimeException e) {
1120                e.printStackTrace();
1121            } catch (IOException e) {
1122                e.printStackTrace();
1123            }
1124            if (p != null) {
1125                int sx = b.getInt("scrollX", 0);
1126                int sy = b.getInt("scrollY", 0);
1127                float scale = b.getFloat("scale", 1.0f);
1128                mDrawHistory = true;
1129                mHistoryPicture = p;
1130                mScrollX = sx;
1131                mScrollY = sy;
1132                mHistoryWidth = Math.round(p.getWidth() * scale);
1133                mHistoryHeight = Math.round(p.getHeight() * scale);
1134                // as getWidth() / getHeight() of the view are not
1135                // available yet, set up mActualScale, so that when
1136                // onSizeChanged() is called, the rest will be set
1137                // correctly
1138                mActualScale = scale;
1139                invalidate();
1140                return true;
1141            }
1142        }
1143        return false;
1144    }
1145
1146    /**
1147     * Restore the state of this WebView from the given map used in
1148     * {@link android.app.Activity#onRestoreInstanceState}. This method should
1149     * be called to restore the state of the WebView before using the object. If
1150     * it is called after the WebView has had a chance to build state (load
1151     * pages, create a back/forward list, etc.) there may be undesirable
1152     * side-effects. Please note that this method no longer restores the
1153     * display data for this WebView. See {@link #savePicture} and {@link
1154     * #restorePicture} for saving and restoring the display data.
1155     * @param inState The incoming Bundle of state.
1156     * @return The restored back/forward list or null if restoreState failed.
1157     * @see #savePicture
1158     * @see #restorePicture
1159     */
1160    public WebBackForwardList restoreState(Bundle inState) {
1161        WebBackForwardList returnList = null;
1162        if (inState == null) {
1163            return returnList;
1164        }
1165        if (inState.containsKey("index") && inState.containsKey("history")) {
1166            mCertificate = SslCertificate.restoreState(
1167                inState.getBundle("certificate"));
1168
1169            final WebBackForwardList list = mCallbackProxy.getBackForwardList();
1170            final int index = inState.getInt("index");
1171            // We can't use a clone of the list because we need to modify the
1172            // shared copy, so synchronize instead to prevent concurrent
1173            // modifications.
1174            synchronized (list) {
1175                final List<byte[]> history =
1176                        (List<byte[]>) inState.getSerializable("history");
1177                final int size = history.size();
1178                // Check the index bounds so we don't crash in native code while
1179                // restoring the history index.
1180                if (index < 0 || index >= size) {
1181                    return null;
1182                }
1183                for (int i = 0; i < size; i++) {
1184                    byte[] data = history.remove(0);
1185                    if (data == null) {
1186                        // If we somehow have null data, we cannot reconstruct
1187                        // the item and thus our history list cannot be rebuilt.
1188                        return null;
1189                    }
1190                    WebHistoryItem item = new WebHistoryItem(data);
1191                    list.addHistoryItem(item);
1192                }
1193                // Grab the most recent copy to return to the caller.
1194                returnList = copyBackForwardList();
1195                // Update the copy to have the correct index.
1196                returnList.setCurrentIndex(index);
1197            }
1198            // Remove all pending messages because we are restoring previous
1199            // state.
1200            mWebViewCore.removeMessages();
1201            // Send a restore state message.
1202            mWebViewCore.sendMessage(EventHub.RESTORE_STATE, index);
1203        }
1204        return returnList;
1205    }
1206
1207    /**
1208     * Load the given url.
1209     * @param url The url of the resource to load.
1210     */
1211    public void loadUrl(String url) {
1212        switchOutDrawHistory();
1213        mWebViewCore.sendMessage(EventHub.LOAD_URL, url);
1214        clearTextEntry();
1215    }
1216
1217    /**
1218     * Load the given data into the WebView. This will load the data into
1219     * WebView using the data: scheme. Content loaded through this mechanism
1220     * does not have the ability to load content from the network.
1221     * @param data A String of data in the given encoding.
1222     * @param mimeType The MIMEType of the data. i.e. text/html, image/jpeg
1223     * @param encoding The encoding of the data. i.e. utf-8, base64
1224     */
1225    public void loadData(String data, String mimeType, String encoding) {
1226        loadUrl("data:" + mimeType + ";" + encoding + "," + data);
1227    }
1228
1229    /**
1230     * Load the given data into the WebView, use the provided URL as the base
1231     * URL for the content. The base URL is the URL that represents the page
1232     * that is loaded through this interface. As such, it is used for the
1233     * history entry and to resolve any relative URLs. The failUrl is used if
1234     * browser fails to load the data provided. If it is empty or null, and the
1235     * load fails, then no history entry is created.
1236     * <p>
1237     * Note for post 1.0. Due to the change in the WebKit, the access to asset
1238     * files through "file:///android_asset/" for the sub resources is more
1239     * restricted. If you provide null or empty string as baseUrl, you won't be
1240     * able to access asset files. If the baseUrl is anything other than
1241     * http(s)/ftp(s)/about/javascript as scheme, you can access asset files for
1242     * sub resources.
1243     *
1244     * @param baseUrl Url to resolve relative paths with, if null defaults to
1245     *            "about:blank"
1246     * @param data A String of data in the given encoding.
1247     * @param mimeType The MIMEType of the data. i.e. text/html. If null,
1248     *            defaults to "text/html"
1249     * @param encoding The encoding of the data. i.e. utf-8, us-ascii
1250     * @param failUrl URL to use if the content fails to load or null.
1251     */
1252    public void loadDataWithBaseURL(String baseUrl, String data,
1253            String mimeType, String encoding, String failUrl) {
1254
1255        if (baseUrl != null && baseUrl.toLowerCase().startsWith("data:")) {
1256            loadData(data, mimeType, encoding);
1257            return;
1258        }
1259        switchOutDrawHistory();
1260        HashMap arg = new HashMap();
1261        arg.put("baseUrl", baseUrl);
1262        arg.put("data", data);
1263        arg.put("mimeType", mimeType);
1264        arg.put("encoding", encoding);
1265        arg.put("failUrl", failUrl);
1266        mWebViewCore.sendMessage(EventHub.LOAD_DATA, arg);
1267        clearTextEntry();
1268    }
1269
1270    /**
1271     * Stop the current load.
1272     */
1273    public void stopLoading() {
1274        // TODO: should we clear all the messages in the queue before sending
1275        // STOP_LOADING?
1276        switchOutDrawHistory();
1277        mWebViewCore.sendMessage(EventHub.STOP_LOADING);
1278    }
1279
1280    /**
1281     * Reload the current url.
1282     */
1283    public void reload() {
1284        switchOutDrawHistory();
1285        mWebViewCore.sendMessage(EventHub.RELOAD);
1286    }
1287
1288    /**
1289     * Return true if this WebView has a back history item.
1290     * @return True iff this WebView has a back history item.
1291     */
1292    public boolean canGoBack() {
1293        WebBackForwardList l = mCallbackProxy.getBackForwardList();
1294        synchronized (l) {
1295            if (l.getClearPending()) {
1296                return false;
1297            } else {
1298                return l.getCurrentIndex() > 0;
1299            }
1300        }
1301    }
1302
1303    /**
1304     * Go back in the history of this WebView.
1305     */
1306    public void goBack() {
1307        goBackOrForward(-1);
1308    }
1309
1310    /**
1311     * Return true if this WebView has a forward history item.
1312     * @return True iff this Webview has a forward history item.
1313     */
1314    public boolean canGoForward() {
1315        WebBackForwardList l = mCallbackProxy.getBackForwardList();
1316        synchronized (l) {
1317            if (l.getClearPending()) {
1318                return false;
1319            } else {
1320                return l.getCurrentIndex() < l.getSize() - 1;
1321            }
1322        }
1323    }
1324
1325    /**
1326     * Go forward in the history of this WebView.
1327     */
1328    public void goForward() {
1329        goBackOrForward(1);
1330    }
1331
1332    /**
1333     * Return true if the page can go back or forward the given
1334     * number of steps.
1335     * @param steps The negative or positive number of steps to move the
1336     *              history.
1337     */
1338    public boolean canGoBackOrForward(int steps) {
1339        WebBackForwardList l = mCallbackProxy.getBackForwardList();
1340        synchronized (l) {
1341            if (l.getClearPending()) {
1342                return false;
1343            } else {
1344                int newIndex = l.getCurrentIndex() + steps;
1345                return newIndex >= 0 && newIndex < l.getSize();
1346            }
1347        }
1348    }
1349
1350    /**
1351     * Go to the history item that is the number of steps away from
1352     * the current item. Steps is negative if backward and positive
1353     * if forward.
1354     * @param steps The number of steps to take back or forward in the back
1355     *              forward list.
1356     */
1357    public void goBackOrForward(int steps) {
1358        goBackOrForward(steps, false);
1359    }
1360
1361    private void goBackOrForward(int steps, boolean ignoreSnapshot) {
1362        // every time we go back or forward, we want to reset the
1363        // WebView certificate:
1364        // if the new site is secure, we will reload it and get a
1365        // new certificate set;
1366        // if the new site is not secure, the certificate must be
1367        // null, and that will be the case
1368        mCertificate = null;
1369        if (steps != 0) {
1370            clearTextEntry();
1371            mWebViewCore.sendMessage(EventHub.GO_BACK_FORWARD, steps,
1372                    ignoreSnapshot ? 1 : 0);
1373        }
1374    }
1375
1376    private boolean extendScroll(int y) {
1377        int finalY = mScroller.getFinalY();
1378        int newY = pinLocY(finalY + y);
1379        if (newY == finalY) return false;
1380        mScroller.setFinalY(newY);
1381        mScroller.extendDuration(computeDuration(0, y));
1382        return true;
1383    }
1384
1385    /**
1386     * Scroll the contents of the view up by half the view size
1387     * @param top true to jump to the top of the page
1388     * @return true if the page was scrolled
1389     */
1390    public boolean pageUp(boolean top) {
1391        if (mNativeClass == 0) {
1392            return false;
1393        }
1394        nativeClearFocus(-1, -1);
1395        if (top) {
1396            // go to the top of the document
1397            return pinScrollTo(mScrollX, 0, true, 0);
1398        }
1399        // Page up
1400        int h = getHeight();
1401        int y;
1402        if (h > 2 * PAGE_SCROLL_OVERLAP) {
1403            y = -h + PAGE_SCROLL_OVERLAP;
1404        } else {
1405            y = -h / 2;
1406        }
1407        mUserScroll = true;
1408        return mScroller.isFinished() ? pinScrollBy(0, y, true, 0)
1409                : extendScroll(y);
1410    }
1411
1412    /**
1413     * Scroll the contents of the view down by half the page size
1414     * @param bottom true to jump to bottom of page
1415     * @return true if the page was scrolled
1416     */
1417    public boolean pageDown(boolean bottom) {
1418        if (mNativeClass == 0) {
1419            return false;
1420        }
1421        nativeClearFocus(-1, -1);
1422        if (bottom) {
1423            return pinScrollTo(mScrollX, mContentHeight, true, 0);
1424        }
1425        // Page down.
1426        int h = getHeight();
1427        int y;
1428        if (h > 2 * PAGE_SCROLL_OVERLAP) {
1429            y = h - PAGE_SCROLL_OVERLAP;
1430        } else {
1431            y = h / 2;
1432        }
1433        mUserScroll = true;
1434        return mScroller.isFinished() ? pinScrollBy(0, y, true, 0)
1435                : extendScroll(y);
1436    }
1437
1438    /**
1439     * Clear the view so that onDraw() will draw nothing but white background,
1440     * and onMeasure() will return 0 if MeasureSpec is not MeasureSpec.EXACTLY
1441     */
1442    public void clearView() {
1443        mContentWidth = 0;
1444        mContentHeight = 0;
1445        mWebViewCore.sendMessage(EventHub.CLEAR_CONTENT);
1446    }
1447
1448    /**
1449     * Return a new picture that captures the current display of the webview.
1450     * This is a copy of the display, and will be unaffected if the webview
1451     * later loads a different URL.
1452     *
1453     * @return a picture containing the current contents of the view. Note this
1454     *         picture is of the entire document, and is not restricted to the
1455     *         bounds of the view.
1456     */
1457    public Picture capturePicture() {
1458        if (null == mWebViewCore) return null; // check for out of memory tab
1459        return mWebViewCore.copyContentPicture();
1460    }
1461
1462    /**
1463     *  Return true if the browser is displaying a TextView for text input.
1464     */
1465    private boolean inEditingMode() {
1466        return mTextEntry != null && mTextEntry.getParent() != null
1467                && mTextEntry.hasFocus();
1468    }
1469
1470    private void clearTextEntry() {
1471        if (inEditingMode()) {
1472            mTextEntry.remove();
1473        }
1474    }
1475
1476    /**
1477     * Return the current scale of the WebView
1478     * @return The current scale.
1479     */
1480    public float getScale() {
1481        return mActualScale;
1482    }
1483
1484    /**
1485     * Set the initial scale for the WebView. 0 means default. If
1486     * {@link WebSettings#getUseWideViewPort()} is true, it zooms out all the
1487     * way. Otherwise it starts with 100%. If initial scale is greater than 0,
1488     * WebView starts will this value as initial scale.
1489     *
1490     * @param scaleInPercent The initial scale in percent.
1491     */
1492    public void setInitialScale(int scaleInPercent) {
1493        mInitialScale = scaleInPercent;
1494    }
1495
1496    /**
1497     * Invoke the graphical zoom picker widget for this WebView. This will
1498     * result in the zoom widget appearing on the screen to control the zoom
1499     * level of this WebView.
1500     */
1501    public void invokeZoomPicker() {
1502        if (!getSettings().supportZoom()) {
1503            Log.w(LOGTAG, "This WebView doesn't support zoom.");
1504            return;
1505        }
1506        clearTextEntry();
1507        if (getSettings().getBuiltInZoomControls()) {
1508            mZoomButtonsController.setVisible(true);
1509        } else {
1510            mPrivateHandler.removeCallbacks(mZoomControlRunnable);
1511            mPrivateHandler.postDelayed(mZoomControlRunnable,
1512                    ZOOM_CONTROLS_TIMEOUT);
1513        }
1514    }
1515
1516    /**
1517     * Return a HitTestResult based on the current focus node. If a HTML::a tag
1518     * is found and the anchor has a non-javascript url, the HitTestResult type
1519     * is set to SRC_ANCHOR_TYPE and the url is set in the "extra" field. If the
1520     * anchor does not have a url or if it is a javascript url, the type will
1521     * be UNKNOWN_TYPE and the url has to be retrieved through
1522     * {@link #requestFocusNodeHref} asynchronously. If a HTML::img tag is
1523     * found, the HitTestResult type is set to IMAGE_TYPE and the url is set in
1524     * the "extra" field. A type of
1525     * SRC_IMAGE_ANCHOR_TYPE indicates an anchor with a url that has an image as
1526     * a child node. If a phone number is found, the HitTestResult type is set
1527     * to PHONE_TYPE and the phone number is set in the "extra" field of
1528     * HitTestResult. If a map address is found, the HitTestResult type is set
1529     * to GEO_TYPE and the address is set in the "extra" field of HitTestResult.
1530     * If an email address is found, the HitTestResult type is set to EMAIL_TYPE
1531     * and the email is set in the "extra" field of HitTestResult. Otherwise,
1532     * HitTestResult type is set to UNKNOWN_TYPE.
1533     */
1534    public HitTestResult getHitTestResult() {
1535        if (mNativeClass == 0) {
1536            return null;
1537        }
1538
1539        HitTestResult result = new HitTestResult();
1540
1541        if (nativeUpdateFocusNode()) {
1542            FocusNode node = mFocusNode;
1543            if (node.mIsTextField || node.mIsTextArea) {
1544                result.setType(HitTestResult.EDIT_TEXT_TYPE);
1545            } else if (node.mText != null) {
1546                String text = node.mText;
1547                if (text.startsWith(SCHEME_TEL)) {
1548                    result.setType(HitTestResult.PHONE_TYPE);
1549                    result.setExtra(text.substring(SCHEME_TEL.length()));
1550                } else if (text.startsWith(SCHEME_MAILTO)) {
1551                    result.setType(HitTestResult.EMAIL_TYPE);
1552                    result.setExtra(text.substring(SCHEME_MAILTO.length()));
1553                } else if (text.startsWith(SCHEME_GEO)) {
1554                    result.setType(HitTestResult.GEO_TYPE);
1555                    result.setExtra(URLDecoder.decode(text
1556                            .substring(SCHEME_GEO.length())));
1557                } else if (node.mIsAnchor) {
1558                    result.setType(HitTestResult.SRC_ANCHOR_TYPE);
1559                    result.setExtra(text);
1560                }
1561            }
1562        }
1563        int type = result.getType();
1564        if (type == HitTestResult.UNKNOWN_TYPE
1565                || type == HitTestResult.SRC_ANCHOR_TYPE) {
1566            // Now check to see if it is an image.
1567            int contentX = viewToContent((int) mLastTouchX + mScrollX);
1568            int contentY = viewToContent((int) mLastTouchY + mScrollY);
1569            String text = nativeImageURI(contentX, contentY);
1570            if (text != null) {
1571                result.setType(type == HitTestResult.UNKNOWN_TYPE ?
1572                        HitTestResult.IMAGE_TYPE :
1573                        HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
1574                result.setExtra(text);
1575            }
1576        }
1577        return result;
1578    }
1579
1580    /**
1581     * Request the href of an anchor element due to getFocusNodePath returning
1582     * "href." If hrefMsg is null, this method returns immediately and does not
1583     * dispatch hrefMsg to its target.
1584     *
1585     * @param hrefMsg This message will be dispatched with the result of the
1586     *            request as the data member with "url" as key. The result can
1587     *            be null.
1588     */
1589    public void requestFocusNodeHref(Message hrefMsg) {
1590        if (hrefMsg == null || mNativeClass == 0) {
1591            return;
1592        }
1593        if (nativeUpdateFocusNode()) {
1594            FocusNode node = mFocusNode;
1595            if (node.mIsAnchor) {
1596                // NOTE: We may already have the url of the anchor stored in
1597                // node.mText but it may be out of date or the caller may want
1598                // to know about javascript urls.
1599                mWebViewCore.sendMessage(EventHub.REQUEST_FOCUS_HREF,
1600                        node.mFramePointer, node.mNodePointer, hrefMsg);
1601            }
1602        }
1603    }
1604
1605    /**
1606     * Request the url of the image last touched by the user. msg will be sent
1607     * to its target with a String representing the url as its object.
1608     *
1609     * @param msg This message will be dispatched with the result of the request
1610     *            as the data member with "url" as key. The result can be null.
1611     */
1612    public void requestImageRef(Message msg) {
1613        int contentX = viewToContent((int) mLastTouchX + mScrollX);
1614        int contentY = viewToContent((int) mLastTouchY + mScrollY);
1615        String ref = nativeImageURI(contentX, contentY);
1616        Bundle data = msg.getData();
1617        data.putString("url", ref);
1618        msg.setData(data);
1619        msg.sendToTarget();
1620    }
1621
1622    private static int pinLoc(int x, int viewMax, int docMax) {
1623//        Log.d(LOGTAG, "-- pinLoc " + x + " " + viewMax + " " + docMax);
1624        if (docMax < viewMax) {   // the doc has room on the sides for "blank"
1625            // pin the short document to the top/left of the screen
1626            x = 0;
1627//            Log.d(LOGTAG, "--- center " + x);
1628        } else if (x < 0) {
1629            x = 0;
1630//            Log.d(LOGTAG, "--- zero");
1631        } else if (x + viewMax > docMax) {
1632            x = docMax - viewMax;
1633//            Log.d(LOGTAG, "--- pin " + x);
1634        }
1635        return x;
1636    }
1637
1638    // Expects x in view coordinates
1639    private int pinLocX(int x) {
1640        return pinLoc(x, getViewWidth(), computeHorizontalScrollRange());
1641    }
1642
1643    // Expects y in view coordinates
1644    private int pinLocY(int y) {
1645        return pinLoc(y, getViewHeight(), computeVerticalScrollRange());
1646    }
1647
1648    /*package*/ int viewToContent(int x) {
1649        return Math.round(x * mInvActualScale);
1650    }
1651
1652    private int contentToView(int x) {
1653        return Math.round(x * mActualScale);
1654    }
1655
1656    // Called by JNI to invalidate the View, given rectangle coordinates in
1657    // content space
1658    private void viewInvalidate(int l, int t, int r, int b) {
1659        invalidate(contentToView(l), contentToView(t), contentToView(r),
1660                contentToView(b));
1661    }
1662
1663    // Called by JNI to invalidate the View after a delay, given rectangle
1664    // coordinates in content space
1665    private void viewInvalidateDelayed(long delay, int l, int t, int r, int b) {
1666        postInvalidateDelayed(delay, contentToView(l), contentToView(t),
1667                contentToView(r), contentToView(b));
1668    }
1669
1670    private Rect contentToView(Rect x) {
1671        return new Rect(contentToView(x.left), contentToView(x.top)
1672                , contentToView(x.right), contentToView(x.bottom));
1673    }
1674
1675    /* call from webcoreview.draw(), so we're still executing in the UI thread
1676    */
1677    private void recordNewContentSize(int w, int h, boolean updateLayout) {
1678
1679        // premature data from webkit, ignore
1680        if ((w | h) == 0) {
1681            return;
1682        }
1683
1684        // don't abort a scroll animation if we didn't change anything
1685        if (mContentWidth != w || mContentHeight != h) {
1686            // record new dimensions
1687            mContentWidth = w;
1688            mContentHeight = h;
1689            // If history Picture is drawn, don't update scroll. They will be
1690            // updated when we get out of that mode.
1691            if (!mDrawHistory) {
1692                // repin our scroll, taking into account the new content size
1693                int oldX = mScrollX;
1694                int oldY = mScrollY;
1695                mScrollX = pinLocX(mScrollX);
1696                mScrollY = pinLocY(mScrollY);
1697                // android.util.Log.d("skia", "recordNewContentSize -
1698                // abortAnimation");
1699                mScroller.abortAnimation(); // just in case
1700                if (oldX != mScrollX || oldY != mScrollY) {
1701                    sendOurVisibleRect();
1702                }
1703            }
1704        }
1705        contentSizeChanged(updateLayout);
1706    }
1707
1708    private void setNewZoomScale(float scale, boolean force) {
1709        if (scale < mMinZoomScale) {
1710            scale = mMinZoomScale;
1711        } else if (scale > mMaxZoomScale) {
1712            scale = mMaxZoomScale;
1713        }
1714        if (scale != mActualScale || force) {
1715            if (mDrawHistory) {
1716                // If history Picture is drawn, don't update scroll. They will
1717                // be updated when we get out of that mode.
1718                if (scale != mActualScale && !mPreviewZoomOnly) {
1719                    mCallbackProxy.onScaleChanged(mActualScale, scale);
1720                }
1721                mActualScale = scale;
1722                mInvActualScale = 1 / scale;
1723                if (!mPreviewZoomOnly) {
1724                    sendViewSizeZoom();
1725                }
1726            } else {
1727                // update our scroll so we don't appear to jump
1728                // i.e. keep the center of the doc in the center of the view
1729
1730                int oldX = mScrollX;
1731                int oldY = mScrollY;
1732                float ratio = scale * mInvActualScale;   // old inverse
1733                float sx = ratio * oldX + (ratio - 1) * mZoomCenterX;
1734                float sy = ratio * oldY + (ratio - 1) * mZoomCenterY;
1735
1736                // now update our new scale and inverse
1737                if (scale != mActualScale && !mPreviewZoomOnly) {
1738                    mCallbackProxy.onScaleChanged(mActualScale, scale);
1739                }
1740                mActualScale = scale;
1741                mInvActualScale = 1 / scale;
1742
1743                // as we don't have animation for scaling, don't do animation
1744                // for scrolling, as it causes weird intermediate state
1745                //        pinScrollTo(Math.round(sx), Math.round(sy));
1746                mScrollX = pinLocX(Math.round(sx));
1747                mScrollY = pinLocY(Math.round(sy));
1748
1749                if (!mPreviewZoomOnly) {
1750                    sendViewSizeZoom();
1751                    sendOurVisibleRect();
1752                }
1753            }
1754        }
1755    }
1756
1757    // Used to avoid sending many visible rect messages.
1758    private Rect mLastVisibleRectSent;
1759    private Rect mLastGlobalRect;
1760
1761    private Rect sendOurVisibleRect() {
1762        Rect rect = new Rect();
1763        calcOurContentVisibleRect(rect);
1764        if (mFindIsUp) {
1765            rect.bottom -= viewToContent(FIND_HEIGHT);
1766        }
1767        // Rect.equals() checks for null input.
1768        if (!rect.equals(mLastVisibleRectSent)) {
1769            mWebViewCore.sendMessage(EventHub.SET_SCROLL_OFFSET,
1770                                     rect.left, rect.top);
1771            mLastVisibleRectSent = rect;
1772        }
1773        Rect globalRect = new Rect();
1774        if (getGlobalVisibleRect(globalRect)
1775                && !globalRect.equals(mLastGlobalRect)) {
1776            // TODO: the global offset is only used by windowRect()
1777            // in ChromeClientAndroid ; other clients such as touch
1778            // and mouse events could return view + screen relative points.
1779            mWebViewCore.sendMessage(EventHub.SET_GLOBAL_BOUNDS, globalRect);
1780            mLastGlobalRect = globalRect;
1781        }
1782        return rect;
1783    }
1784
1785    // Sets r to be the visible rectangle of our webview in view coordinates
1786    private void calcOurVisibleRect(Rect r) {
1787        Point p = new Point();
1788        getGlobalVisibleRect(r, p);
1789        r.offset(-p.x, -p.y);
1790    }
1791
1792    // Sets r to be our visible rectangle in content coordinates
1793    private void calcOurContentVisibleRect(Rect r) {
1794        calcOurVisibleRect(r);
1795        r.left = viewToContent(r.left);
1796        r.top = viewToContent(r.top);
1797        r.right = viewToContent(r.right);
1798        r.bottom = viewToContent(r.bottom);
1799    }
1800
1801    /**
1802     * Compute unzoomed width and height, and if they differ from the last
1803     * values we sent, send them to webkit (to be used has new viewport)
1804     *
1805     * @return true if new values were sent
1806     */
1807    private boolean sendViewSizeZoom() {
1808        int newWidth = Math.round(getViewWidth() * mInvActualScale);
1809        int newHeight = Math.round(getViewHeight() * mInvActualScale);
1810        /*
1811         * Because the native side may have already done a layout before the
1812         * View system was able to measure us, we have to send a height of 0 to
1813         * remove excess whitespace when we grow our width. This will trigger a
1814         * layout and a change in content size. This content size change will
1815         * mean that contentSizeChanged will either call this method directly or
1816         * indirectly from onSizeChanged.
1817         */
1818        if (newWidth > mLastWidthSent && mWrapContent) {
1819            newHeight = 0;
1820        }
1821        // Avoid sending another message if the dimensions have not changed.
1822        if (newWidth != mLastWidthSent || newHeight != mLastHeightSent) {
1823            mWebViewCore.sendMessage(EventHub.VIEW_SIZE_CHANGED,
1824                    newWidth, newHeight, new Float(mActualScale));
1825            mLastWidthSent = newWidth;
1826            mLastHeightSent = newHeight;
1827            return true;
1828        }
1829        return false;
1830    }
1831
1832    @Override
1833    protected int computeHorizontalScrollRange() {
1834        if (mDrawHistory) {
1835            return mHistoryWidth;
1836        } else {
1837            return contentToView(mContentWidth);
1838        }
1839    }
1840
1841    // Make sure this stays in sync with the actual height of the FindDialog.
1842    private static final int FIND_HEIGHT = 79;
1843
1844    @Override
1845    protected int computeVerticalScrollRange() {
1846        if (mDrawHistory) {
1847            return mHistoryHeight;
1848        } else {
1849            int height = contentToView(mContentHeight);
1850            if (mFindIsUp) {
1851                height += FIND_HEIGHT;
1852            }
1853            return height;
1854        }
1855    }
1856
1857    /**
1858     * Get the url for the current page. This is not always the same as the url
1859     * passed to WebViewClient.onPageStarted because although the load for
1860     * that url has begun, the current page may not have changed.
1861     * @return The url for the current page.
1862     */
1863    public String getUrl() {
1864        WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
1865        return h != null ? h.getUrl() : null;
1866    }
1867
1868    /**
1869     * Get the original url for the current page. This is not always the same
1870     * as the url passed to WebViewClient.onPageStarted because although the
1871     * load for that url has begun, the current page may not have changed.
1872     * Also, there may have been redirects resulting in a different url to that
1873     * originally requested.
1874     * @return The url that was originally requested for the current page.
1875     */
1876    public String getOriginalUrl() {
1877        WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
1878        return h != null ? h.getOriginalUrl() : null;
1879    }
1880
1881    /**
1882     * Get the title for the current page. This is the title of the current page
1883     * until WebViewClient.onReceivedTitle is called.
1884     * @return The title for the current page.
1885     */
1886    public String getTitle() {
1887        WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
1888        return h != null ? h.getTitle() : null;
1889    }
1890
1891    /**
1892     * Get the favicon for the current page. This is the favicon of the current
1893     * page until WebViewClient.onReceivedIcon is called.
1894     * @return The favicon for the current page.
1895     */
1896    public Bitmap getFavicon() {
1897        WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
1898        return h != null ? h.getFavicon() : null;
1899    }
1900
1901    /**
1902     * Get the progress for the current page.
1903     * @return The progress for the current page between 0 and 100.
1904     */
1905    public int getProgress() {
1906        return mCallbackProxy.getProgress();
1907    }
1908
1909    /**
1910     * @return the height of the HTML content.
1911     */
1912    public int getContentHeight() {
1913        return mContentHeight;
1914    }
1915
1916    /**
1917     * Pause all layout, parsing, and javascript timers. This can be useful if
1918     * the WebView is not visible or the application has been paused.
1919     */
1920    public void pauseTimers() {
1921        mWebViewCore.sendMessage(EventHub.PAUSE_TIMERS);
1922    }
1923
1924    /**
1925     * Resume all layout, parsing, and javascript timers. This will resume
1926     * dispatching all timers.
1927     */
1928    public void resumeTimers() {
1929        mWebViewCore.sendMessage(EventHub.RESUME_TIMERS);
1930    }
1931
1932    /**
1933     * Clear the resource cache. Note that the cache is per-application, so
1934     * this will clear the cache for all WebViews used.
1935     *
1936     * @param includeDiskFiles If false, only the RAM cache is cleared.
1937     */
1938    public void clearCache(boolean includeDiskFiles) {
1939        // Note: this really needs to be a static method as it clears cache for all
1940        // WebView. But we need mWebViewCore to send message to WebCore thread, so
1941        // we can't make this static.
1942        mWebViewCore.sendMessage(EventHub.CLEAR_CACHE,
1943                includeDiskFiles ? 1 : 0, 0);
1944    }
1945
1946    /**
1947     * Make sure that clearing the form data removes the adapter from the
1948     * currently focused textfield if there is one.
1949     */
1950    public void clearFormData() {
1951        if (inEditingMode()) {
1952            AutoCompleteAdapter adapter = null;
1953            mTextEntry.setAdapterCustom(adapter);
1954        }
1955    }
1956
1957    /**
1958     * Tell the WebView to clear its internal back/forward list.
1959     */
1960    public void clearHistory() {
1961        mCallbackProxy.getBackForwardList().setClearPending();
1962        mWebViewCore.sendMessage(EventHub.CLEAR_HISTORY);
1963    }
1964
1965    /**
1966     * Clear the SSL preferences table stored in response to proceeding with SSL
1967     * certificate errors.
1968     */
1969    public void clearSslPreferences() {
1970        mWebViewCore.sendMessage(EventHub.CLEAR_SSL_PREF_TABLE);
1971    }
1972
1973    /**
1974     * Return the WebBackForwardList for this WebView. This contains the
1975     * back/forward list for use in querying each item in the history stack.
1976     * This is a copy of the private WebBackForwardList so it contains only a
1977     * snapshot of the current state. Multiple calls to this method may return
1978     * different objects. The object returned from this method will not be
1979     * updated to reflect any new state.
1980     */
1981    public WebBackForwardList copyBackForwardList() {
1982        return mCallbackProxy.getBackForwardList().clone();
1983    }
1984
1985    /*
1986     * Highlight and scroll to the next occurance of String in findAll.
1987     * Wraps the page infinitely, and scrolls.  Must be called after
1988     * calling findAll.
1989     *
1990     * @param forward Direction to search.
1991     */
1992    public void findNext(boolean forward) {
1993        nativeFindNext(forward);
1994    }
1995
1996    /*
1997     * Find all instances of find on the page and highlight them.
1998     * @param find  String to find.
1999     * @return int  The number of occurances of the String "find"
2000     *              that were found.
2001     */
2002    public int findAll(String find) {
2003        mFindIsUp = true;
2004        int result = nativeFindAll(find.toLowerCase(), find.toUpperCase());
2005        invalidate();
2006        return result;
2007    }
2008
2009    // Used to know whether the find dialog is open.  Affects whether
2010    // or not we draw the highlights for matches.
2011    private boolean mFindIsUp;
2012
2013    private native int nativeFindAll(String findLower, String findUpper);
2014    private native void nativeFindNext(boolean forward);
2015
2016    /**
2017     * Return the first substring consisting of the address of a physical
2018     * location. Currently, only addresses in the United States are detected,
2019     * and consist of:
2020     * - a house number
2021     * - a street name
2022     * - a street type (Road, Circle, etc), either spelled out or abbreviated
2023     * - a city name
2024     * - a state or territory, either spelled out or two-letter abbr.
2025     * - an optional 5 digit or 9 digit zip code.
2026     *
2027     * All names must be correctly capitalized, and the zip code, if present,
2028     * must be valid for the state. The street type must be a standard USPS
2029     * spelling or abbreviation. The state or territory must also be spelled
2030     * or abbreviated using USPS standards. The house number may not exceed
2031     * five digits.
2032     * @param addr The string to search for addresses.
2033     *
2034     * @return the address, or if no address is found, return null.
2035     */
2036    public static String findAddress(String addr) {
2037        return WebViewCore.nativeFindAddress(addr);
2038    }
2039
2040    /*
2041     * Clear the highlighting surrounding text matches created by findAll.
2042     */
2043    public void clearMatches() {
2044        mFindIsUp = false;
2045        nativeSetFindIsDown();
2046        // Now that the dialog has been removed, ensure that we scroll to a
2047        // location that is not beyond the end of the page.
2048        pinScrollTo(mScrollX, mScrollY, false, 0);
2049        invalidate();
2050    }
2051
2052    /**
2053     * Query the document to see if it contains any image references. The
2054     * message object will be dispatched with arg1 being set to 1 if images
2055     * were found and 0 if the document does not reference any images.
2056     * @param response The message that will be dispatched with the result.
2057     */
2058    public void documentHasImages(Message response) {
2059        if (response == null) {
2060            return;
2061        }
2062        mWebViewCore.sendMessage(EventHub.DOC_HAS_IMAGES, response);
2063    }
2064
2065    @Override
2066    public void computeScroll() {
2067        if (mScroller.computeScrollOffset()) {
2068            int oldX = mScrollX;
2069            int oldY = mScrollY;
2070            mScrollX = mScroller.getCurrX();
2071            mScrollY = mScroller.getCurrY();
2072            postInvalidate();  // So we draw again
2073            if (oldX != mScrollX || oldY != mScrollY) {
2074                // as onScrollChanged() is not called, sendOurVisibleRect()
2075                // needs to be call explicitly
2076                sendOurVisibleRect();
2077            }
2078        } else {
2079            super.computeScroll();
2080        }
2081    }
2082
2083    private static int computeDuration(int dx, int dy) {
2084        int distance = Math.max(Math.abs(dx), Math.abs(dy));
2085        int duration = distance * 1000 / STD_SPEED;
2086        return Math.min(duration, MAX_DURATION);
2087    }
2088
2089    // helper to pin the scrollBy parameters (already in view coordinates)
2090    // returns true if the scroll was changed
2091    private boolean pinScrollBy(int dx, int dy, boolean animate, int animationDuration) {
2092        return pinScrollTo(mScrollX + dx, mScrollY + dy, animate, animationDuration);
2093    }
2094
2095    // helper to pin the scrollTo parameters (already in view coordinates)
2096    // returns true if the scroll was changed
2097    private boolean pinScrollTo(int x, int y, boolean animate, int animationDuration) {
2098        x = pinLocX(x);
2099        y = pinLocY(y);
2100        int dx = x - mScrollX;
2101        int dy = y - mScrollY;
2102
2103        if ((dx | dy) == 0) {
2104            return false;
2105        }
2106
2107        if (true && animate) {
2108            //        Log.d(LOGTAG, "startScroll: " + dx + " " + dy);
2109
2110            mScroller.startScroll(mScrollX, mScrollY, dx, dy,
2111                    animationDuration > 0 ? animationDuration : computeDuration(dx, dy));
2112            invalidate();
2113        } else {
2114            mScroller.abortAnimation(); // just in case
2115            scrollTo(x, y);
2116        }
2117        return true;
2118    }
2119
2120    // Scale from content to view coordinates, and pin.
2121    // Also called by jni webview.cpp
2122    private void setContentScrollBy(int cx, int cy, boolean animate) {
2123        if (mDrawHistory) {
2124            // disallow WebView to change the scroll position as History Picture
2125            // is used in the view system.
2126            // TODO: as we switchOutDrawHistory when trackball or navigation
2127            // keys are hit, this should be safe. Right?
2128            return;
2129        }
2130        cx = contentToView(cx);
2131        cy = contentToView(cy);
2132        if (mHeightCanMeasure) {
2133            // move our visible rect according to scroll request
2134            if (cy != 0) {
2135                Rect tempRect = new Rect();
2136                calcOurVisibleRect(tempRect);
2137                tempRect.offset(cx, cy);
2138                requestRectangleOnScreen(tempRect);
2139            }
2140            // FIXME: We scroll horizontally no matter what because currently
2141            // ScrollView and ListView will not scroll horizontally.
2142            // FIXME: Why do we only scroll horizontally if there is no
2143            // vertical scroll?
2144//                Log.d(LOGTAG, "setContentScrollBy cy=" + cy);
2145            if (cy == 0 && cx != 0) {
2146                pinScrollBy(cx, 0, animate, 0);
2147            }
2148        } else {
2149            pinScrollBy(cx, cy, animate, 0);
2150        }
2151    }
2152
2153    // scale from content to view coordinates, and pin
2154    // return true if pin caused the final x/y different than the request cx/cy;
2155    // return false if the view scroll to the exact position as it is requested.
2156    private boolean setContentScrollTo(int cx, int cy) {
2157        if (mDrawHistory) {
2158            // disallow WebView to change the scroll position as History Picture
2159            // is used in the view system.
2160            // One known case where this is called is that WebCore tries to
2161            // restore the scroll position. As history Picture already uses the
2162            // saved scroll position, it is ok to skip this.
2163            return false;
2164        }
2165        int vx = contentToView(cx);
2166        int vy = contentToView(cy);
2167//        Log.d(LOGTAG, "content scrollTo [" + cx + " " + cy + "] view=[" +
2168//                      vx + " " + vy + "]");
2169        pinScrollTo(vx, vy, false, 0);
2170        if (mScrollX != vx || mScrollY != vy) {
2171            return true;
2172        } else {
2173            return false;
2174        }
2175    }
2176
2177    // scale from content to view coordinates, and pin
2178    private void spawnContentScrollTo(int cx, int cy) {
2179        if (mDrawHistory) {
2180            // disallow WebView to change the scroll position as History Picture
2181            // is used in the view system.
2182            return;
2183        }
2184        int vx = contentToView(cx);
2185        int vy = contentToView(cy);
2186        pinScrollTo(vx, vy, true, 0);
2187    }
2188
2189    /**
2190     * These are from webkit, and are in content coordinate system (unzoomed)
2191     */
2192    private void contentSizeChanged(boolean updateLayout) {
2193        // suppress 0,0 since we usually see real dimensions soon after
2194        // this avoids drawing the prev content in a funny place. If we find a
2195        // way to consolidate these notifications, this check may become
2196        // obsolete
2197        if ((mContentWidth | mContentHeight) == 0) {
2198            return;
2199        }
2200
2201        if (mHeightCanMeasure) {
2202            if (getMeasuredHeight() != contentToView(mContentHeight)
2203                    && updateLayout) {
2204                requestLayout();
2205            }
2206        } else if (mWidthCanMeasure) {
2207            if (getMeasuredWidth() != contentToView(mContentWidth)
2208                    && updateLayout) {
2209                requestLayout();
2210            }
2211        } else {
2212            // If we don't request a layout, try to send our view size to the
2213            // native side to ensure that WebCore has the correct dimensions.
2214            sendViewSizeZoom();
2215        }
2216    }
2217
2218    /**
2219     * Set the WebViewClient that will receive various notifications and
2220     * requests. This will replace the current handler.
2221     * @param client An implementation of WebViewClient.
2222     */
2223    public void setWebViewClient(WebViewClient client) {
2224        mCallbackProxy.setWebViewClient(client);
2225    }
2226
2227    /**
2228     * Register the interface to be used when content can not be handled by
2229     * the rendering engine, and should be downloaded instead. This will replace
2230     * the current handler.
2231     * @param listener An implementation of DownloadListener.
2232     */
2233    public void setDownloadListener(DownloadListener listener) {
2234        mCallbackProxy.setDownloadListener(listener);
2235    }
2236
2237    /**
2238     * Set the chrome handler. This is an implementation of WebChromeClient for
2239     * use in handling Javascript dialogs, favicons, titles, and the progress.
2240     * This will replace the current handler.
2241     * @param client An implementation of WebChromeClient.
2242     */
2243    public void setWebChromeClient(WebChromeClient client) {
2244        mCallbackProxy.setWebChromeClient(client);
2245    }
2246
2247    /**
2248     * Set the Picture listener. This is an interface used to receive
2249     * notifications of a new Picture.
2250     * @param listener An implementation of WebView.PictureListener.
2251     */
2252    public void setPictureListener(PictureListener listener) {
2253        mPictureListener = listener;
2254    }
2255
2256    /**
2257     * {@hide}
2258     */
2259    /* FIXME: Debug only! Remove for SDK! */
2260    public void externalRepresentation(Message callback) {
2261        mWebViewCore.sendMessage(EventHub.REQUEST_EXT_REPRESENTATION, callback);
2262    }
2263
2264    /**
2265     * {@hide}
2266     */
2267    /* FIXME: Debug only! Remove for SDK! */
2268    public void documentAsText(Message callback) {
2269        mWebViewCore.sendMessage(EventHub.REQUEST_DOC_AS_TEXT, callback);
2270    }
2271
2272    /**
2273     * Use this function to bind an object to Javascript so that the
2274     * methods can be accessed from Javascript.
2275     * <p><strong>IMPORTANT:</strong>
2276     * <ul>
2277     * <li> Using addJavascriptInterface() allows JavaScript to control your
2278     * application. This can be a very useful feature or a dangerous security
2279     * issue. When the HTML in the WebView is untrustworthy (for example, part
2280     * or all of the HTML is provided by some person or process), then an
2281     * attacker could inject HTML that will execute your code and possibly any
2282     * code of the attacker's choosing.<br>
2283     * Do not use addJavascriptInterface() unless all of the HTML in this
2284     * WebView was written by you.</li>
2285     * <li> The Java object that is bound runs in another thread and not in
2286     * the thread that it was constructed in.</li>
2287     * </ul></p>
2288     * @param obj The class instance to bind to Javascript
2289     * @param interfaceName The name to used to expose the class in Javascript
2290     */
2291    public void addJavascriptInterface(Object obj, String interfaceName) {
2292        // Use Hashmap rather than Bundle as Bundles can't cope with Objects
2293        HashMap arg = new HashMap();
2294        arg.put("object", obj);
2295        arg.put("interfaceName", interfaceName);
2296        mWebViewCore.sendMessage(EventHub.ADD_JS_INTERFACE, arg);
2297    }
2298
2299    /**
2300     * Return the WebSettings object used to control the settings for this
2301     * WebView.
2302     * @return A WebSettings object that can be used to control this WebView's
2303     *         settings.
2304     */
2305    public WebSettings getSettings() {
2306        return mWebViewCore.getSettings();
2307    }
2308
2309   /**
2310    * Return the list of currently loaded plugins.
2311    * @return The list of currently loaded plugins.
2312    */
2313    public static synchronized PluginList getPluginList() {
2314        if (sPluginList == null) {
2315            sPluginList = new PluginList();
2316        }
2317        return sPluginList;
2318    }
2319
2320   /**
2321     * TODO: need to add @Deprecated
2322    */
2323    public void refreshPlugins(boolean reloadOpenPages) {
2324        PluginManager.getInstance(mContext).refreshPlugins(reloadOpenPages);
2325    }
2326
2327    //-------------------------------------------------------------------------
2328    // Override View methods
2329    //-------------------------------------------------------------------------
2330
2331    @Override
2332    protected void finalize() throws Throwable {
2333        destroy();
2334    }
2335
2336    @Override
2337    protected void onDraw(Canvas canvas) {
2338        // if mNativeClass is 0, the WebView has been destroyed. Do nothing.
2339        if (mNativeClass == 0) {
2340            return;
2341        }
2342        if (mWebViewCore.mEndScaleZoom) {
2343            mWebViewCore.mEndScaleZoom = false;
2344            if (mTouchMode >= FIRST_SCROLL_ZOOM
2345                    && mTouchMode <= LAST_SCROLL_ZOOM) {
2346                setHorizontalScrollBarEnabled(true);
2347                setVerticalScrollBarEnabled(true);
2348                mTouchMode = TOUCH_DONE_MODE;
2349            }
2350        }
2351        int sc = canvas.save();
2352        if (mTouchMode >= FIRST_SCROLL_ZOOM && mTouchMode <= LAST_SCROLL_ZOOM) {
2353            scrollZoomDraw(canvas);
2354        } else {
2355            nativeRecomputeFocus();
2356            // Update the buttons in the picture, so when we draw the picture
2357            // to the screen, they are in the correct state.
2358            // Tell the native side if user is a) touching the screen,
2359            // b) pressing the trackball down, or c) pressing the enter key
2360            // If the focus is a button, we need to draw it in the pressed
2361            // state.
2362            // If mNativeClass is 0, we should not reach here, so we do not
2363            // need to check it again.
2364            nativeRecordButtons(hasFocus() && hasWindowFocus(),
2365                    mTouchMode == TOUCH_SHORTPRESS_START_MODE
2366                    || mTrackballDown || mGotEnterDown, false);
2367            drawCoreAndFocusRing(canvas, mBackgroundColor, mDrawFocusRing);
2368        }
2369        canvas.restoreToCount(sc);
2370
2371        if (AUTO_REDRAW_HACK && mAutoRedraw) {
2372            invalidate();
2373        }
2374    }
2375
2376    @Override
2377    public void setLayoutParams(ViewGroup.LayoutParams params) {
2378        if (params.height == LayoutParams.WRAP_CONTENT) {
2379            mWrapContent = true;
2380        }
2381        super.setLayoutParams(params);
2382    }
2383
2384    @Override
2385    public boolean performLongClick() {
2386        if (inEditingMode()) {
2387            return mTextEntry.performLongClick();
2388        } else {
2389            return super.performLongClick();
2390        }
2391    }
2392
2393    private void drawCoreAndFocusRing(Canvas canvas, int color,
2394        boolean drawFocus) {
2395        if (mDrawHistory) {
2396            canvas.scale(mActualScale, mActualScale);
2397            canvas.drawPicture(mHistoryPicture);
2398            return;
2399        }
2400
2401        boolean animateZoom = mZoomScale != 0;
2402        boolean animateScroll = !mScroller.isFinished()
2403                || mVelocityTracker != null;
2404        if (animateZoom) {
2405            float zoomScale;
2406            int interval = (int) (SystemClock.uptimeMillis() - mZoomStart);
2407            if (interval < ZOOM_ANIMATION_LENGTH) {
2408                float ratio = (float) interval / ZOOM_ANIMATION_LENGTH;
2409                zoomScale = 1.0f / (mInvInitialZoomScale
2410                        + (mInvFinalZoomScale - mInvInitialZoomScale) * ratio);
2411                invalidate();
2412            } else {
2413                zoomScale = mZoomScale;
2414                // set mZoomScale to be 0 as we have done animation
2415                mZoomScale = 0;
2416            }
2417            float scale = (mActualScale - zoomScale) * mInvActualScale;
2418            float tx = scale * (mZoomCenterX + mScrollX);
2419            float ty = scale * (mZoomCenterY + mScrollY);
2420
2421            // this block pins the translate to "legal" bounds. This makes the
2422            // animation a bit non-obvious, but it means we won't pop when the
2423            // "real" zoom takes effect
2424            if (true) {
2425               // canvas.translate(mScrollX, mScrollY);
2426                tx -= mScrollX;
2427                ty -= mScrollY;
2428                tx = -pinLoc(-Math.round(tx), getViewWidth(), Math
2429                        .round(mContentWidth * zoomScale));
2430                ty = -pinLoc(-Math.round(ty), getViewHeight(), Math
2431                        .round(mContentHeight * zoomScale));
2432                tx += mScrollX;
2433                ty += mScrollY;
2434            }
2435            canvas.translate(tx, ty);
2436            canvas.scale(zoomScale, zoomScale);
2437        } else {
2438            canvas.scale(mActualScale, mActualScale);
2439        }
2440
2441        mWebViewCore.drawContentPicture(canvas, color, animateZoom,
2442                animateScroll);
2443
2444        if (mNativeClass == 0) return;
2445        if (mShiftIsPressed) {
2446            if (mTouchSelection) {
2447                nativeDrawSelectionRegion(canvas);
2448            } else {
2449                nativeDrawSelection(canvas, mSelectX, mSelectY,
2450                        mExtendSelection);
2451            }
2452        } else if (drawFocus) {
2453            if (mTouchMode == TOUCH_SHORTPRESS_START_MODE) {
2454                mTouchMode = TOUCH_SHORTPRESS_MODE;
2455                HitTestResult hitTest = getHitTestResult();
2456                if (hitTest != null &&
2457                        hitTest.mType != HitTestResult.UNKNOWN_TYPE) {
2458                    mPrivateHandler.sendMessageDelayed(mPrivateHandler
2459                            .obtainMessage(SWITCH_TO_LONGPRESS),
2460                            LONG_PRESS_TIMEOUT);
2461                }
2462            }
2463            nativeDrawFocusRing(canvas);
2464        }
2465        // When the FindDialog is up, only draw the matches if we are not in
2466        // the process of scrolling them into view.
2467        if (mFindIsUp && !animateScroll) {
2468            nativeDrawMatches(canvas);
2469        }
2470    }
2471
2472    private native void nativeDrawMatches(Canvas canvas);
2473
2474    private float scrollZoomGridScale(float invScale) {
2475        float griddedInvScale = (int) (invScale * SCROLL_ZOOM_GRID)
2476            / (float) SCROLL_ZOOM_GRID;
2477        return 1.0f / griddedInvScale;
2478    }
2479
2480    private float scrollZoomX(float scale) {
2481        int width = getViewWidth();
2482        float maxScrollZoomX = mContentWidth * scale - width;
2483        int maxX = mContentWidth - width;
2484        return -(maxScrollZoomX > 0 ? mZoomScrollX * maxScrollZoomX / maxX
2485                : maxScrollZoomX / 2);
2486    }
2487
2488    private float scrollZoomY(float scale) {
2489        int height = getViewHeight();
2490        float maxScrollZoomY = mContentHeight * scale - height;
2491        int maxY = mContentHeight - height;
2492        return -(maxScrollZoomY > 0 ? mZoomScrollY * maxScrollZoomY / maxY
2493                : maxScrollZoomY / 2);
2494    }
2495
2496    private void drawMagnifyFrame(Canvas canvas, Rect frame, Paint paint) {
2497        final float ADORNMENT_LEN = 16.0f;
2498        float width = frame.width();
2499        float height = frame.height();
2500        Path path = new Path();
2501        path.moveTo(-ADORNMENT_LEN, -ADORNMENT_LEN);
2502        path.lineTo(0, 0);
2503        path.lineTo(width, 0);
2504        path.lineTo(width + ADORNMENT_LEN, -ADORNMENT_LEN);
2505        path.moveTo(-ADORNMENT_LEN, height + ADORNMENT_LEN);
2506        path.lineTo(0, height);
2507        path.lineTo(width, height);
2508        path.lineTo(width + ADORNMENT_LEN, height + ADORNMENT_LEN);
2509        path.moveTo(0, 0);
2510        path.lineTo(0, height);
2511        path.moveTo(width, 0);
2512        path.lineTo(width, height);
2513        path.offset(frame.left, frame.top);
2514        canvas.drawPath(path, paint);
2515    }
2516
2517    // Returns frame surrounding magified portion of screen while
2518    // scroll-zoom is enabled. The frame is also used to center the
2519    // zoom-in zoom-out points at the start and end of the animation.
2520    private Rect scrollZoomFrame(int width, int height, float halfScale) {
2521        Rect scrollFrame = new Rect();
2522        scrollFrame.set(mZoomScrollX, mZoomScrollY,
2523                mZoomScrollX + width, mZoomScrollY + height);
2524        if (mContentWidth * mZoomScrollLimit < width) {
2525            float scale = zoomFrameScaleX(width, halfScale, 1.0f);
2526            float offsetX = (width * scale - width) * 0.5f;
2527            scrollFrame.left -= offsetX;
2528            scrollFrame.right += offsetX;
2529        }
2530        if (mContentHeight * mZoomScrollLimit < height) {
2531            float scale = zoomFrameScaleY(height, halfScale, 1.0f);
2532            float offsetY = (height * scale - height) * 0.5f;
2533            scrollFrame.top -= offsetY;
2534            scrollFrame.bottom += offsetY;
2535        }
2536        return scrollFrame;
2537    }
2538
2539    private float zoomFrameScaleX(int width, float halfScale, float noScale) {
2540        // mContentWidth > width > mContentWidth * mZoomScrollLimit
2541        if (mContentWidth <= width) {
2542            return halfScale;
2543        }
2544        float part = (width - mContentWidth * mZoomScrollLimit)
2545                / (width * (1 - mZoomScrollLimit));
2546        return halfScale * part + noScale * (1.0f - part);
2547    }
2548
2549    private float zoomFrameScaleY(int height, float halfScale, float noScale) {
2550        if (mContentHeight <= height) {
2551            return halfScale;
2552        }
2553        float part = (height - mContentHeight * mZoomScrollLimit)
2554                / (height * (1 - mZoomScrollLimit));
2555        return halfScale * part + noScale * (1.0f - part);
2556    }
2557
2558    private float scrollZoomMagScale(float invScale) {
2559        return (invScale * 2 + mInvActualScale) / 3;
2560    }
2561
2562    private void scrollZoomDraw(Canvas canvas) {
2563        float invScale = mZoomScrollInvLimit;
2564        int elapsed = 0;
2565        if (mTouchMode != SCROLL_ZOOM_OUT) {
2566            elapsed = (int) Math.min(System.currentTimeMillis()
2567                - mZoomScrollStart, SCROLL_ZOOM_DURATION);
2568            float transitionScale = (mZoomScrollInvLimit - mInvActualScale)
2569                    * elapsed / SCROLL_ZOOM_DURATION;
2570            if (mTouchMode == SCROLL_ZOOM_ANIMATION_OUT) {
2571                invScale = mInvActualScale + transitionScale;
2572            } else { /* if (mTouchMode == SCROLL_ZOOM_ANIMATION_IN) */
2573                invScale = mZoomScrollInvLimit - transitionScale;
2574            }
2575        }
2576        float scale = scrollZoomGridScale(invScale);
2577        invScale = 1.0f / scale;
2578        int width = getViewWidth();
2579        int height = getViewHeight();
2580        float halfScale = scrollZoomMagScale(invScale);
2581        Rect scrollFrame = scrollZoomFrame(width, height, halfScale);
2582        if (elapsed == SCROLL_ZOOM_DURATION) {
2583            if (mTouchMode == SCROLL_ZOOM_ANIMATION_IN) {
2584                setHorizontalScrollBarEnabled(true);
2585                setVerticalScrollBarEnabled(true);
2586                updateTextEntry();
2587                scrollTo((int) (scrollFrame.centerX() * mActualScale)
2588                        - (width >> 1), (int) (scrollFrame.centerY()
2589                        * mActualScale) - (height >> 1));
2590                mTouchMode = TOUCH_DONE_MODE;
2591            } else {
2592                mTouchMode = SCROLL_ZOOM_OUT;
2593            }
2594        }
2595        float newX = scrollZoomX(scale);
2596        float newY = scrollZoomY(scale);
2597        if (LOGV_ENABLED) {
2598            Log.v(LOGTAG, "scrollZoomDraw scale=" + scale + " + (" + newX
2599                    + ", " + newY + ") mZoomScroll=(" + mZoomScrollX + ", "
2600                    + mZoomScrollY + ")" + " invScale=" + invScale + " scale="
2601                    + scale);
2602        }
2603        canvas.translate(newX, newY);
2604        canvas.scale(scale, scale);
2605        boolean animating = mTouchMode != SCROLL_ZOOM_OUT;
2606        if (mDrawHistory) {
2607            int sc = canvas.save(Canvas.CLIP_SAVE_FLAG);
2608            Rect clip = new Rect(0, 0, mHistoryPicture.getWidth(),
2609                    mHistoryPicture.getHeight());
2610            canvas.clipRect(clip, Region.Op.DIFFERENCE);
2611            canvas.drawColor(mBackgroundColor);
2612            canvas.restoreToCount(sc);
2613            canvas.drawPicture(mHistoryPicture);
2614        } else {
2615            mWebViewCore.drawContentPicture(canvas, mBackgroundColor,
2616                    animating, true);
2617        }
2618        if (mTouchMode == TOUCH_DONE_MODE) {
2619            return;
2620        }
2621        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
2622        paint.setStyle(Paint.Style.STROKE);
2623        paint.setStrokeWidth(30.0f);
2624        paint.setARGB(0x50, 0, 0, 0);
2625        int maxX = mContentWidth - width;
2626        int maxY = mContentHeight - height;
2627        if (true) { // experiment: draw hint to place finger off magnify area
2628            drawMagnifyFrame(canvas, scrollFrame, paint);
2629        } else {
2630            canvas.drawRect(scrollFrame, paint);
2631        }
2632        int sc = canvas.save();
2633        canvas.clipRect(scrollFrame);
2634        float halfX = (float) mZoomScrollX / maxX;
2635        if (mContentWidth * mZoomScrollLimit < width) {
2636            halfX = zoomFrameScaleX(width, 0.5f, halfX);
2637        }
2638        float halfY = (float) mZoomScrollY / maxY;
2639        if (mContentHeight * mZoomScrollLimit < height) {
2640            halfY = zoomFrameScaleY(height, 0.5f, halfY);
2641        }
2642        canvas.scale(halfScale, halfScale, mZoomScrollX + width * halfX
2643                , mZoomScrollY + height * halfY);
2644        if (LOGV_ENABLED) {
2645            Log.v(LOGTAG, "scrollZoomDraw halfScale=" + halfScale + " w/h=("
2646                    + width + ", " + height + ") half=(" + halfX + ", "
2647                    + halfY + ")");
2648        }
2649        if (mDrawHistory) {
2650            canvas.drawPicture(mHistoryPicture);
2651        } else {
2652            mWebViewCore.drawContentPicture(canvas, mBackgroundColor,
2653                    animating, false);
2654        }
2655        canvas.restoreToCount(sc);
2656        if (mTouchMode != SCROLL_ZOOM_OUT) {
2657            invalidate();
2658        }
2659    }
2660
2661    private void zoomScrollTap(float x, float y) {
2662        float scale = scrollZoomGridScale(mZoomScrollInvLimit);
2663        float left = scrollZoomX(scale);
2664        float top = scrollZoomY(scale);
2665        int width = getViewWidth();
2666        int height = getViewHeight();
2667        x -= width * scale / 2;
2668        y -= height * scale / 2;
2669        mZoomScrollX = Math.min(mContentWidth - width
2670                , Math.max(0, (int) ((x - left) / scale)));
2671        mZoomScrollY = Math.min(mContentHeight - height
2672                , Math.max(0, (int) ((y - top) / scale)));
2673        if (LOGV_ENABLED) {
2674            Log.v(LOGTAG, "zoomScrollTap scale=" + scale + " + (" + left
2675                    + ", " + top + ") mZoomScroll=(" + mZoomScrollX + ", "
2676                    + mZoomScrollY + ")" + " x=" + x + " y=" + y);
2677        }
2678    }
2679
2680    private boolean canZoomScrollOut() {
2681        if (mContentWidth == 0 || mContentHeight == 0) {
2682            return false;
2683        }
2684        int width = getViewWidth();
2685        int height = getViewHeight();
2686        float x = (float) width / (float) mContentWidth;
2687        float y = (float) height / (float) mContentHeight;
2688        mZoomScrollLimit = Math.max(DEFAULT_MIN_ZOOM_SCALE, Math.min(x, y));
2689        mZoomScrollInvLimit = 1.0f / mZoomScrollLimit;
2690        if (LOGV_ENABLED) {
2691            Log.v(LOGTAG, "canZoomScrollOut"
2692                    + " mInvActualScale=" + mInvActualScale
2693                    + " mZoomScrollLimit=" + mZoomScrollLimit
2694                    + " mZoomScrollInvLimit=" + mZoomScrollInvLimit
2695                    + " mContentWidth=" + mContentWidth
2696                    + " mContentHeight=" + mContentHeight
2697                    );
2698        }
2699        // don't zoom out unless magnify area is at least half as wide
2700        // or tall as content
2701        float limit = mZoomScrollLimit * 2;
2702        return mContentWidth >= width * limit
2703                || mContentHeight >= height * limit;
2704    }
2705
2706    private void startZoomScrollOut() {
2707        setHorizontalScrollBarEnabled(false);
2708        setVerticalScrollBarEnabled(false);
2709        if (getSettings().getBuiltInZoomControls()) {
2710            if (mZoomButtonsController.isVisible()) {
2711                mZoomButtonsController.setVisible(false);
2712            }
2713        } else {
2714            if (mZoomControlRunnable != null) {
2715                mPrivateHandler.removeCallbacks(mZoomControlRunnable);
2716            }
2717            if (mZoomControls != null) {
2718                mZoomControls.hide();
2719            }
2720        }
2721        int width = getViewWidth();
2722        int height = getViewHeight();
2723        int halfW = width >> 1;
2724        mLastTouchX = halfW;
2725        int halfH = height >> 1;
2726        mLastTouchY = halfH;
2727        mScroller.abortAnimation();
2728        mZoomScrollStart = System.currentTimeMillis();
2729        Rect zoomFrame = scrollZoomFrame(width, height
2730                , scrollZoomMagScale(mZoomScrollInvLimit));
2731        mZoomScrollX = Math.max(0, (int) ((mScrollX + halfW) * mInvActualScale)
2732                - (zoomFrame.width() >> 1));
2733        mZoomScrollY = Math.max(0, (int) ((mScrollY + halfH) * mInvActualScale)
2734                - (zoomFrame.height() >> 1));
2735        scrollTo(0, 0); // triggers inval, starts animation
2736        clearTextEntry();
2737        if (LOGV_ENABLED) {
2738            Log.v(LOGTAG, "startZoomScrollOut mZoomScroll=("
2739                    + mZoomScrollX + ", " + mZoomScrollY +")");
2740        }
2741    }
2742
2743    private void zoomScrollOut() {
2744        if (canZoomScrollOut() == false) {
2745            mTouchMode = TOUCH_DONE_MODE;
2746            return;
2747        }
2748        startZoomScrollOut();
2749        mTouchMode = SCROLL_ZOOM_ANIMATION_OUT;
2750        invalidate();
2751    }
2752
2753    private void moveZoomScrollWindow(float x, float y) {
2754        if (Math.abs(x - mLastZoomScrollRawX) < 1.5f
2755                && Math.abs(y - mLastZoomScrollRawY) < 1.5f) {
2756            return;
2757        }
2758        mLastZoomScrollRawX = x;
2759        mLastZoomScrollRawY = y;
2760        int oldX = mZoomScrollX;
2761        int oldY = mZoomScrollY;
2762        int width = getViewWidth();
2763        int height = getViewHeight();
2764        int maxZoomX = mContentWidth - width;
2765        if (maxZoomX > 0) {
2766            int maxScreenX = width - (int) Math.ceil(width
2767                    * mZoomScrollLimit) - SCROLL_ZOOM_FINGER_BUFFER;
2768            if (LOGV_ENABLED) {
2769                Log.v(LOGTAG, "moveZoomScrollWindow-X"
2770                        + " maxScreenX=" + maxScreenX + " width=" + width
2771                        + " mZoomScrollLimit=" + mZoomScrollLimit + " x=" + x);
2772            }
2773            x += maxScreenX * mLastScrollX / maxZoomX - mLastTouchX;
2774            x *= Math.max(maxZoomX / maxScreenX, mZoomScrollInvLimit);
2775            mZoomScrollX = Math.max(0, Math.min(maxZoomX, (int) x));
2776        }
2777        int maxZoomY = mContentHeight - height;
2778        if (maxZoomY > 0) {
2779            int maxScreenY = height - (int) Math.ceil(height
2780                    * mZoomScrollLimit) - SCROLL_ZOOM_FINGER_BUFFER;
2781            if (LOGV_ENABLED) {
2782                Log.v(LOGTAG, "moveZoomScrollWindow-Y"
2783                        + " maxScreenY=" + maxScreenY + " height=" + height
2784                        + " mZoomScrollLimit=" + mZoomScrollLimit + " y=" + y);
2785            }
2786            y += maxScreenY * mLastScrollY / maxZoomY - mLastTouchY;
2787            y *= Math.max(maxZoomY / maxScreenY, mZoomScrollInvLimit);
2788            mZoomScrollY = Math.max(0, Math.min(maxZoomY, (int) y));
2789        }
2790        if (oldX != mZoomScrollX || oldY != mZoomScrollY) {
2791            invalidate();
2792        }
2793        if (LOGV_ENABLED) {
2794            Log.v(LOGTAG, "moveZoomScrollWindow"
2795                    + " scrollTo=(" + mZoomScrollX + ", " + mZoomScrollY + ")"
2796                    + " mLastTouch=(" + mLastTouchX + ", " + mLastTouchY + ")"
2797                    + " maxZoom=(" + maxZoomX + ", " + maxZoomY + ")"
2798                    + " last=("+mLastScrollX+", "+mLastScrollY+")"
2799                    + " x=" + x + " y=" + y);
2800        }
2801    }
2802
2803    private void setZoomScrollIn() {
2804        mZoomScrollStart = System.currentTimeMillis();
2805    }
2806
2807    private float mZoomScrollLimit;
2808    private float mZoomScrollInvLimit;
2809    private int mLastScrollX;
2810    private int mLastScrollY;
2811    private long mZoomScrollStart;
2812    private int mZoomScrollX;
2813    private int mZoomScrollY;
2814    private float mLastZoomScrollRawX = -1000.0f;
2815    private float mLastZoomScrollRawY = -1000.0f;
2816    // The zoomed scale varies from 1.0 to DEFAULT_MIN_ZOOM_SCALE == 0.25.
2817    // The zoom animation duration SCROLL_ZOOM_DURATION == 0.5.
2818    // Two pressures compete for gridding; a high frame rate (e.g. 20 fps)
2819    // and minimizing font cache allocations (fewer frames is better).
2820    // A SCROLL_ZOOM_GRID of 6 permits about 20 zoom levels over 0.5 seconds:
2821    // the inverse of: 1.0, 1.16, 1.33, 1.5, 1.67, 1.84, 2.0, etc. to 4.0
2822    private static final int SCROLL_ZOOM_GRID = 6;
2823    private static final int SCROLL_ZOOM_DURATION = 500;
2824    // Make it easier to get to the bottom of a document by reserving a 32
2825    // pixel buffer, for when the starting drag is a bit below the bottom of
2826    // the magnify frame.
2827    private static final int SCROLL_ZOOM_FINGER_BUFFER = 32;
2828
2829    // draw history
2830    private boolean mDrawHistory = false;
2831    private Picture mHistoryPicture = null;
2832    private int mHistoryWidth = 0;
2833    private int mHistoryHeight = 0;
2834
2835    // Only check the flag, can be called from WebCore thread
2836    boolean drawHistory() {
2837        return mDrawHistory;
2838    }
2839
2840    // Should only be called in UI thread
2841    void switchOutDrawHistory() {
2842        if (null == mWebViewCore) return; // CallbackProxy may trigger this
2843        if (mDrawHistory && mWebViewCore.pictureReady()) {
2844            mDrawHistory = false;
2845            invalidate();
2846            int oldScrollX = mScrollX;
2847            int oldScrollY = mScrollY;
2848            mScrollX = pinLocX(mScrollX);
2849            mScrollY = pinLocY(mScrollY);
2850            if (oldScrollX != mScrollX || oldScrollY != mScrollY) {
2851                mUserScroll = false;
2852                mWebViewCore.sendMessage(EventHub.SYNC_SCROLL, oldScrollX,
2853                        oldScrollY);
2854            }
2855            sendOurVisibleRect();
2856        }
2857    }
2858
2859    /**
2860     *  Class representing the node which is focused.
2861     */
2862    private class FocusNode {
2863        public FocusNode() {
2864            mBounds = new Rect();
2865        }
2866        // Only to be called by JNI
2867        private void setAll(boolean isTextField, boolean isTextArea, boolean
2868                isPassword, boolean isAnchor, boolean isRtlText, int maxLength,
2869                int textSize, int boundsX, int boundsY, int boundsRight, int
2870                boundsBottom, int nodePointer, int framePointer, String text,
2871                String name, int rootTextGeneration) {
2872            mIsTextField        = isTextField;
2873            mIsTextArea         = isTextArea;
2874            mIsPassword         = isPassword;
2875            mIsAnchor           = isAnchor;
2876            mIsRtlText          = isRtlText;
2877
2878            mMaxLength          = maxLength;
2879            mTextSize           = textSize;
2880
2881            mBounds.set(boundsX, boundsY, boundsRight, boundsBottom);
2882
2883
2884            mNodePointer        = nodePointer;
2885            mFramePointer       = framePointer;
2886            mText               = text;
2887            mName               = name;
2888            mRootTextGeneration = rootTextGeneration;
2889        }
2890        public boolean  mIsTextField;
2891        public boolean  mIsTextArea;
2892        public boolean  mIsPassword;
2893        public boolean  mIsAnchor;
2894        public boolean  mIsRtlText;
2895
2896        public int      mSelectionStart;
2897        public int      mSelectionEnd;
2898        public int      mMaxLength;
2899        public int      mTextSize;
2900
2901        public Rect     mBounds;
2902
2903        public int      mNodePointer;
2904        public int      mFramePointer;
2905        public String   mText;
2906        public String   mName;
2907        public int      mRootTextGeneration;
2908    }
2909
2910    // Warning: ONLY use mFocusNode AFTER calling nativeUpdateFocusNode(),
2911    // and ONLY if it returns true;
2912    private FocusNode mFocusNode = new FocusNode();
2913
2914    /**
2915     *  Delete text from start to end in the focused textfield. If there is no
2916     *  focus, or if start == end, silently fail.  If start and end are out of
2917     *  order, swap them.
2918     *  @param  start   Beginning of selection to delete.
2919     *  @param  end     End of selection to delete.
2920     */
2921    /* package */ void deleteSelection(int start, int end) {
2922        mTextGeneration++;
2923        mWebViewCore.sendMessage(EventHub.DELETE_SELECTION, start, end,
2924                new WebViewCore.FocusData(mFocusData));
2925    }
2926
2927    /**
2928     *  Set the selection to (start, end) in the focused textfield. If start and
2929     *  end are out of order, swap them.
2930     *  @param  start   Beginning of selection.
2931     *  @param  end     End of selection.
2932     */
2933    /* package */ void setSelection(int start, int end) {
2934        mWebViewCore.sendMessage(EventHub.SET_SELECTION, start, end,
2935                new WebViewCore.FocusData(mFocusData));
2936    }
2937
2938    // Called by JNI when a touch event puts a textfield into focus.
2939    private void displaySoftKeyboard() {
2940        InputMethodManager imm = (InputMethodManager)
2941                getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
2942        imm.showSoftInput(mTextEntry, 0);
2943        mTextEntry.enableScrollOnScreen(true);
2944        // Now we need to fake a touch event to place the cursor where the
2945        // user touched.
2946        AbsoluteLayout.LayoutParams lp = (AbsoluteLayout.LayoutParams)
2947                mTextEntry.getLayoutParams();
2948        if (lp != null) {
2949            // Take the last touch and adjust for the location of the
2950            // TextDialog.
2951            float x = mLastTouchX + (float) (mScrollX - lp.x);
2952            float y = mLastTouchY + (float) (mScrollY - lp.y);
2953            mTextEntry.fakeTouchEvent(x, y);
2954        }
2955    }
2956
2957    private void updateTextEntry() {
2958        // If we do not have focus, do nothing until we gain focus.
2959        if (!hasFocus() && (null == mTextEntry || !mTextEntry.hasFocus())
2960                || (mTouchMode >= FIRST_SCROLL_ZOOM
2961                && mTouchMode <= LAST_SCROLL_ZOOM)) {
2962            mNeedsUpdateTextEntry = true;
2963            return;
2964        }
2965        boolean alreadyThere = inEditingMode();
2966        // inEditingMode can only return true if mTextEntry is non-null,
2967        // so we can safely call remove() if (alreadyThere)
2968        if (0 == mNativeClass || !nativeUpdateFocusNode()) {
2969            if (alreadyThere) {
2970                mTextEntry.remove();
2971            }
2972            return;
2973        }
2974        FocusNode node = mFocusNode;
2975        if (!node.mIsTextField && !node.mIsTextArea) {
2976            if (alreadyThere) {
2977                mTextEntry.remove();
2978            }
2979            return;
2980        }
2981        // At this point, we know we have found an input field, so go ahead
2982        // and create the TextDialog if necessary.
2983        if (mTextEntry == null) {
2984            mTextEntry = new TextDialog(mContext, WebView.this);
2985            // Initialize our generation number.
2986            mTextGeneration = 0;
2987        }
2988        mTextEntry.setTextSize(contentToView(node.mTextSize));
2989        Rect visibleRect = sendOurVisibleRect();
2990        // Note that sendOurVisibleRect calls viewToContent, so the coordinates
2991        // should be in content coordinates.
2992        if (!Rect.intersects(node.mBounds, visibleRect)) {
2993            // Node is not on screen, so do not bother.
2994            return;
2995        }
2996        int x = node.mBounds.left;
2997        int y = node.mBounds.top;
2998        int width = node.mBounds.width();
2999        int height = node.mBounds.height();
3000        if (alreadyThere && mTextEntry.isSameTextField(node.mNodePointer)) {
3001            // It is possible that we have the same textfield, but it has moved,
3002            // i.e. In the case of opening/closing the screen.
3003            // In that case, we need to set the dimensions, but not the other
3004            // aspects.
3005            // We also need to restore the selection, which gets wrecked by
3006            // calling setTextEntryRect.
3007            Spannable spannable = (Spannable) mTextEntry.getText();
3008            int start = Selection.getSelectionStart(spannable);
3009            int end = Selection.getSelectionEnd(spannable);
3010            setTextEntryRect(x, y, width, height);
3011            // If the text has been changed by webkit, update it.  However, if
3012            // there has been more UI text input, ignore it.  We will receive
3013            // another update when that text is recognized.
3014            if (node.mText != null && !node.mText.equals(spannable.toString())
3015                    && node.mRootTextGeneration == mTextGeneration) {
3016                mTextEntry.setTextAndKeepSelection(node.mText);
3017            } else {
3018                Selection.setSelection(spannable, start, end);
3019            }
3020        } else {
3021            String text = node.mText;
3022            setTextEntryRect(x, y, width, height);
3023            mTextEntry.setGravity(node.mIsRtlText ? Gravity.RIGHT :
3024                    Gravity.NO_GRAVITY);
3025            // this needs to be called before update adapter thread starts to
3026            // ensure the mTextEntry has the same node pointer
3027            mTextEntry.setNodePointer(node.mNodePointer);
3028            int maxLength = -1;
3029            if (node.mIsTextField) {
3030                maxLength = node.mMaxLength;
3031                if (mWebViewCore.getSettings().getSaveFormData()
3032                        && node.mName != null) {
3033                    HashMap data = new HashMap();
3034                    data.put("text", node.mText);
3035                    Message update = mPrivateHandler.obtainMessage(
3036                            UPDATE_TEXT_ENTRY_ADAPTER, node.mNodePointer, 0,
3037                            data);
3038                    UpdateTextEntryAdapter updater = new UpdateTextEntryAdapter(
3039                            node.mName, getUrl(), update);
3040                    Thread t = new Thread(updater);
3041                    t.start();
3042                }
3043            }
3044            mTextEntry.setMaxLength(maxLength);
3045            AutoCompleteAdapter adapter = null;
3046            mTextEntry.setAdapterCustom(adapter);
3047            mTextEntry.setSingleLine(node.mIsTextField);
3048            mTextEntry.setInPassword(node.mIsPassword);
3049            if (null == text) {
3050                mTextEntry.setText("", 0, 0);
3051            } else {
3052                // Change to true to enable the old style behavior, where
3053                // entering a textfield/textarea always set the selection to the
3054                // whole field.  This was desirable for the case where the user
3055                // intends to scroll past the field using the trackball.
3056                // However, it causes a problem when replying to emails - the
3057                // user expects the cursor to be at the beginning of the
3058                // textarea.  Testing out a new behavior, where textfields set
3059                // selection at the end, and textareas at the beginning.
3060                if (false) {
3061                    mTextEntry.setText(text, 0, text.length());
3062                } else if (node.mIsTextField) {
3063                    int length = text.length();
3064                    mTextEntry.setText(text, length, length);
3065                } else {
3066                    mTextEntry.setText(text, 0, 0);
3067                }
3068            }
3069            mTextEntry.requestFocus();
3070        }
3071    }
3072
3073    private class UpdateTextEntryAdapter implements Runnable {
3074        private String mName;
3075        private String mUrl;
3076        private Message mUpdateMessage;
3077
3078        public UpdateTextEntryAdapter(String name, String url, Message msg) {
3079            mName = name;
3080            mUrl = url;
3081            mUpdateMessage = msg;
3082        }
3083
3084        public void run() {
3085            ArrayList<String> pastEntries = mDatabase.getFormData(mUrl, mName);
3086            if (pastEntries.size() > 0) {
3087                AutoCompleteAdapter adapter = new
3088                        AutoCompleteAdapter(mContext, pastEntries);
3089                ((HashMap) mUpdateMessage.obj).put("adapter", adapter);
3090                mUpdateMessage.sendToTarget();
3091            }
3092        }
3093    }
3094
3095    private void setTextEntryRect(int x, int y, int width, int height) {
3096        x = contentToView(x);
3097        y = contentToView(y);
3098        width = contentToView(width);
3099        height = contentToView(height);
3100        mTextEntry.setRect(x, y, width, height);
3101    }
3102
3103    // This is used to determine long press with the enter key, or
3104    // a center key.  Does not affect long press with the trackball/touch.
3105    private boolean mGotEnterDown = false;
3106
3107    @Override
3108    public boolean onKeyDown(int keyCode, KeyEvent event) {
3109        if (LOGV_ENABLED) {
3110            Log.v(LOGTAG, "keyDown at " + System.currentTimeMillis()
3111                    + ", " + event);
3112        }
3113
3114        if (mNativeClass == 0) {
3115            return false;
3116        }
3117
3118        // do this hack up front, so it always works, regardless of touch-mode
3119        if (AUTO_REDRAW_HACK && (keyCode == KeyEvent.KEYCODE_CALL)) {
3120            mAutoRedraw = !mAutoRedraw;
3121            if (mAutoRedraw) {
3122                invalidate();
3123            }
3124            return true;
3125        }
3126
3127        // Bubble up the key event if
3128        // 1. it is a system key; or
3129        // 2. the host application wants to handle it; or
3130        // 3. webview is in scroll-zoom state;
3131        if (event.isSystem()
3132                || mCallbackProxy.uiOverrideKeyEvent(event)
3133                || (mTouchMode >= FIRST_SCROLL_ZOOM && mTouchMode <= LAST_SCROLL_ZOOM)) {
3134            return false;
3135        }
3136
3137        if (mShiftIsPressed == false && nativeFocusNodeWantsKeyEvents() == false
3138                && (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
3139                || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT)) {
3140            mExtendSelection = false;
3141            mShiftIsPressed = true;
3142            if (nativeUpdateFocusNode()) {
3143                FocusNode node = mFocusNode;
3144                mSelectX = contentToView(node.mBounds.left);
3145                mSelectY = contentToView(node.mBounds.top);
3146            } else {
3147                mSelectX = mScrollX + (int) mLastTouchX;
3148                mSelectY = mScrollY + (int) mLastTouchY;
3149            }
3150            int contentX = viewToContent((int) mLastTouchX + mScrollX);
3151            int contentY = viewToContent((int) mLastTouchY + mScrollY);
3152            nativeClearFocus(contentX, contentY);
3153       }
3154
3155        if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
3156                && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
3157            // always handle the navigation keys in the UI thread
3158            switchOutDrawHistory();
3159            if (navHandledKey(keyCode, 1, false, event.getEventTime())) {
3160                playSoundEffect(keyCodeToSoundsEffect(keyCode));
3161                return true;
3162            }
3163            // Bubble up the key event as WebView doesn't handle it
3164            return false;
3165        }
3166
3167        if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
3168                || keyCode == KeyEvent.KEYCODE_ENTER) {
3169            switchOutDrawHistory();
3170            if (event.getRepeatCount() == 0) {
3171                mGotEnterDown = true;
3172                mPrivateHandler.sendMessageDelayed(mPrivateHandler
3173                        .obtainMessage(LONG_PRESS_ENTER), LONG_PRESS_TIMEOUT);
3174                // Already checked mNativeClass, so we do not need to check it
3175                // again.
3176                nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true);
3177                return true;
3178            }
3179            // Bubble up the key event as WebView doesn't handle it
3180            return false;
3181        }
3182
3183        if (keyCode != KeyEvent.KEYCODE_SHIFT_LEFT
3184                && keyCode != KeyEvent.KEYCODE_SHIFT_RIGHT) {
3185            // turn off copy select if a shift-key combo is pressed
3186            mExtendSelection = mShiftIsPressed = false;
3187            if (mTouchMode == TOUCH_SELECT_MODE) {
3188                mTouchMode = TOUCH_INIT_MODE;
3189            }
3190        }
3191
3192        if (getSettings().getNavDump()) {
3193            switch (keyCode) {
3194                case KeyEvent.KEYCODE_4:
3195                    // "/data/data/com.android.browser/displayTree.txt"
3196                    nativeDumpDisplayTree(getUrl());
3197                    break;
3198                case KeyEvent.KEYCODE_5:
3199                case KeyEvent.KEYCODE_6:
3200                    // 5: dump the dom tree to the file
3201                    // "/data/data/com.android.browser/domTree.txt"
3202                    // 6: dump the dom tree to the adb log
3203                    mWebViewCore.sendMessage(EventHub.DUMP_DOMTREE,
3204                            (keyCode == KeyEvent.KEYCODE_5) ? 1 : 0, 0);
3205                    break;
3206                case KeyEvent.KEYCODE_7:
3207                case KeyEvent.KEYCODE_8:
3208                    // 7: dump the render tree to the file
3209                    // "/data/data/com.android.browser/renderTree.txt"
3210                    // 8: dump the render tree to the adb log
3211                    mWebViewCore.sendMessage(EventHub.DUMP_RENDERTREE,
3212                            (keyCode == KeyEvent.KEYCODE_7) ? 1 : 0, 0);
3213                    break;
3214                case KeyEvent.KEYCODE_9:
3215                    nativeInstrumentReport();
3216                    return true;
3217            }
3218        }
3219
3220        // TODO: should we pass all the keys to DOM or check the meta tag
3221        if (nativeFocusNodeWantsKeyEvents() || true) {
3222            // pass the key to DOM
3223            mWebViewCore.sendMessage(EventHub.KEY_DOWN, event);
3224            // return true as DOM handles the key
3225            return true;
3226        }
3227
3228        // Bubble up the key event as WebView doesn't handle it
3229        return false;
3230    }
3231
3232    @Override
3233    public boolean onKeyUp(int keyCode, KeyEvent event) {
3234        if (LOGV_ENABLED) {
3235            Log.v(LOGTAG, "keyUp at " + System.currentTimeMillis()
3236                    + ", " + event);
3237        }
3238
3239        if (mNativeClass == 0) {
3240            return false;
3241        }
3242
3243        // special CALL handling when focus node's href is "tel:XXX"
3244        if (keyCode == KeyEvent.KEYCODE_CALL && nativeUpdateFocusNode()) {
3245            FocusNode node = mFocusNode;
3246            String text = node.mText;
3247            if (!node.mIsTextField && !node.mIsTextArea && text != null
3248                    && text.startsWith(SCHEME_TEL)) {
3249                Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(text));
3250                getContext().startActivity(intent);
3251                return true;
3252            }
3253        }
3254
3255        // Bubble up the key event if
3256        // 1. it is a system key; or
3257        // 2. the host application wants to handle it;
3258        if (event.isSystem() || mCallbackProxy.uiOverrideKeyEvent(event)) {
3259            return false;
3260        }
3261
3262        // special handling in scroll_zoom state
3263        if (mTouchMode >= FIRST_SCROLL_ZOOM && mTouchMode <= LAST_SCROLL_ZOOM) {
3264            if (KeyEvent.KEYCODE_DPAD_CENTER == keyCode
3265                    && mTouchMode != SCROLL_ZOOM_ANIMATION_IN) {
3266                setZoomScrollIn();
3267                mTouchMode = SCROLL_ZOOM_ANIMATION_IN;
3268                invalidate();
3269                return true;
3270            }
3271            return false;
3272        }
3273
3274        if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
3275                || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
3276            if (commitCopy()) {
3277                return true;
3278            }
3279        }
3280
3281        if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
3282                && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
3283            // always handle the navigation keys in the UI thread
3284            // Bubble up the key event as WebView doesn't handle it
3285            return false;
3286        }
3287
3288        if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
3289                || keyCode == KeyEvent.KEYCODE_ENTER) {
3290            // remove the long press message first
3291            mPrivateHandler.removeMessages(LONG_PRESS_ENTER);
3292            mGotEnterDown = false;
3293
3294            if (KeyEvent.KEYCODE_DPAD_CENTER == keyCode) {
3295                if (mShiftIsPressed) {
3296                    return false;
3297                }
3298                if (getSettings().supportZoom()) {
3299                    if (mTouchMode == TOUCH_DOUBLECLICK_MODE) {
3300                        zoomScrollOut();
3301                    } else {
3302                        if (LOGV_ENABLED) {
3303                            Log.v(LOGTAG, "TOUCH_DOUBLECLICK_MODE");
3304                        }
3305                        mPrivateHandler.sendMessageDelayed(mPrivateHandler
3306                                .obtainMessage(SWITCH_TO_ENTER), TAP_TIMEOUT);
3307                        mTouchMode = TOUCH_DOUBLECLICK_MODE;
3308                    }
3309                    return true;
3310                }
3311            }
3312
3313            Rect visibleRect = sendOurVisibleRect();
3314            // Note that sendOurVisibleRect calls viewToContent, so the
3315            // coordinates should be in content coordinates.
3316            if (nativeUpdateFocusNode()) {
3317                if (Rect.intersects(mFocusNode.mBounds, visibleRect)) {
3318                    nativeSetFollowedLink(true);
3319                    mWebViewCore.sendMessage(EventHub.SET_FINAL_FOCUS,
3320                            EventHub.BLOCK_FOCUS_CHANGE_UNTIL_KEY_UP, 0,
3321                            new WebViewCore.FocusData(mFocusData));
3322                    playSoundEffect(SoundEffectConstants.CLICK);
3323                    if (!mCallbackProxy.uiOverrideUrlLoading(mFocusNode.mText)) {
3324                        // use CLICK instead of KEY_DOWN/KEY_UP so that we can
3325                        // trigger mouse click events
3326                        mWebViewCore.sendMessage(EventHub.CLICK);
3327                    }
3328                }
3329                return true;
3330            }
3331            // Bubble up the key event as WebView doesn't handle it
3332            return false;
3333        }
3334
3335        // TODO: should we pass all the keys to DOM or check the meta tag
3336        if (nativeFocusNodeWantsKeyEvents() || true) {
3337            // pass the key to DOM
3338            mWebViewCore.sendMessage(EventHub.KEY_UP, event);
3339            // return true as DOM handles the key
3340            return true;
3341        }
3342
3343        // Bubble up the key event as WebView doesn't handle it
3344        return false;
3345    }
3346
3347    /**
3348     * @hide
3349     */
3350    public void emulateShiftHeld() {
3351        mExtendSelection = false;
3352        mShiftIsPressed = true;
3353        int contentX = viewToContent((int) mLastTouchX + mScrollX);
3354        int contentY = viewToContent((int) mLastTouchY + mScrollY);
3355        nativeClearFocus(contentX, contentY);
3356    }
3357
3358    private boolean commitCopy() {
3359        boolean copiedSomething = false;
3360        if (mExtendSelection) {
3361            // copy region so core operates on copy without touching orig.
3362            Region selection = new Region(nativeGetSelection());
3363            if (selection.isEmpty() == false) {
3364                Toast.makeText(mContext
3365                        , com.android.internal.R.string.text_copied
3366                        , Toast.LENGTH_SHORT).show();
3367                mWebViewCore.sendMessage(EventHub.GET_SELECTION, selection);
3368                copiedSomething = true;
3369            }
3370            mExtendSelection = false;
3371        }
3372        mShiftIsPressed = false;
3373        if (mTouchMode == TOUCH_SELECT_MODE) {
3374            mTouchMode = TOUCH_INIT_MODE;
3375        }
3376        return copiedSomething;
3377    }
3378
3379    // Set this as a hierarchy change listener so we can know when this view
3380    // is removed and still have access to our parent.
3381    @Override
3382    protected void onAttachedToWindow() {
3383        super.onAttachedToWindow();
3384        ViewParent parent = getParent();
3385        if (parent instanceof ViewGroup) {
3386            ViewGroup p = (ViewGroup) parent;
3387            p.setOnHierarchyChangeListener(this);
3388        }
3389    }
3390
3391    @Override
3392    protected void onDetachedFromWindow() {
3393        super.onDetachedFromWindow();
3394        ViewParent parent = getParent();
3395        if (parent instanceof ViewGroup) {
3396            ViewGroup p = (ViewGroup) parent;
3397            p.setOnHierarchyChangeListener(null);
3398        }
3399
3400        // Clean up the zoom controller
3401        mZoomButtonsController.setVisible(false);
3402    }
3403
3404    // Implementation for OnHierarchyChangeListener
3405    public void onChildViewAdded(View parent, View child) {}
3406
3407    public void onChildViewRemoved(View p, View child) {
3408        if (child == this) {
3409            if (inEditingMode()) {
3410                clearTextEntry();
3411                mNeedsUpdateTextEntry = true;
3412            }
3413        }
3414    }
3415
3416    /**
3417     * @deprecated WebView should not have implemented
3418     * ViewTreeObserver.OnGlobalFocusChangeListener.  This method
3419     * does nothing now.
3420     */
3421    @Deprecated
3422    public void onGlobalFocusChanged(View oldFocus, View newFocus) {
3423    }
3424
3425    // To avoid drawing the focus ring, and remove the TextView when our window
3426    // loses focus.
3427    @Override
3428    public void onWindowFocusChanged(boolean hasWindowFocus) {
3429        if (hasWindowFocus) {
3430            if (hasFocus()) {
3431                // If our window regained focus, and we have focus, then begin
3432                // drawing the focus ring, and restore the TextView if
3433                // necessary.
3434                mDrawFocusRing = true;
3435                if (mNeedsUpdateTextEntry) {
3436                    updateTextEntry();
3437                }
3438                if (mNativeClass != 0) {
3439                    nativeRecordButtons(true, false, true);
3440                }
3441                setFocusControllerActive(true);
3442            } else {
3443                // If our window gained focus, but we do not have it, do not
3444                // draw the focus ring.
3445                mDrawFocusRing = false;
3446                // We do not call nativeRecordButtons here because we assume
3447                // that when we lost focus, or window focus, it got called with
3448                // false for the first parameter
3449            }
3450        } else {
3451            if (getSettings().getBuiltInZoomControls() && !mZoomButtonsController.isVisible()) {
3452                /*
3453                 * The zoom controls come in their own window, so our window
3454                 * loses focus. Our policy is to not draw the focus ring if
3455                 * our window is not focused, but this is an exception since
3456                 * the user can still navigate the web page with the zoom
3457                 * controls showing.
3458                 */
3459                // If our window has lost focus, stop drawing the focus ring
3460                mDrawFocusRing = false;
3461            }
3462            mGotKeyDown = false;
3463            mShiftIsPressed = false;
3464            if (mNativeClass != 0) {
3465                nativeRecordButtons(false, false, true);
3466            }
3467            setFocusControllerActive(false);
3468        }
3469        invalidate();
3470        super.onWindowFocusChanged(hasWindowFocus);
3471    }
3472
3473    /*
3474     * Pass a message to WebCore Thread, determining whether the WebCore::Page's
3475     * FocusController is "active" so that it will draw the blinking cursor.
3476     */
3477    private void setFocusControllerActive(boolean active) {
3478        if (mWebViewCore != null) {
3479            mWebViewCore.sendMessage(EventHub.SET_ACTIVE, active ? 1 : 0, 0);
3480        }
3481    }
3482
3483    @Override
3484    protected void onFocusChanged(boolean focused, int direction,
3485            Rect previouslyFocusedRect) {
3486        if (LOGV_ENABLED) {
3487            Log.v(LOGTAG, "MT focusChanged " + focused + ", " + direction);
3488        }
3489        if (focused) {
3490            // When we regain focus, if we have window focus, resume drawing
3491            // the focus ring, and add the TextView if necessary.
3492            if (hasWindowFocus()) {
3493                mDrawFocusRing = true;
3494                if (mNeedsUpdateTextEntry) {
3495                    updateTextEntry();
3496                    mNeedsUpdateTextEntry = false;
3497                }
3498                if (mNativeClass != 0) {
3499                    nativeRecordButtons(true, false, true);
3500                }
3501                // FIXME: This is unnecessary if we are gaining focus from the
3502                // TextDialog.  How can we tell if it was the last thing in
3503                // focus?
3504                setFocusControllerActive(true);
3505            //} else {
3506                // The WebView has gained focus while we do not have
3507                // windowfocus.  When our window lost focus, we should have
3508                // called nativeRecordButtons(false...)
3509            }
3510        } else {
3511            // When we lost focus, unless focus went to the TextView (which is
3512            // true if we are in editing mode), stop drawing the focus ring.
3513            if (!inEditingMode()) {
3514                mDrawFocusRing = false;
3515                if (mNativeClass != 0) {
3516                    nativeRecordButtons(false, false, true);
3517                }
3518                setFocusControllerActive(false);
3519            }
3520            mGotKeyDown = false;
3521        }
3522
3523        super.onFocusChanged(focused, direction, previouslyFocusedRect);
3524    }
3525
3526    @Override
3527    protected void onSizeChanged(int w, int h, int ow, int oh) {
3528        super.onSizeChanged(w, h, ow, oh);
3529        // Center zooming to the center of the screen.
3530        mZoomCenterX = getViewWidth() * .5f;
3531        mZoomCenterY = getViewHeight() * .5f;
3532
3533        // update mMinZoomScale if the minimum zoom scale is not fixed
3534        if (!mMinZoomScaleFixed) {
3535            mMinZoomScale = (float) getViewWidth()
3536                    / Math.max(ZOOM_OUT_WIDTH, mContentWidth);
3537        }
3538
3539        // we always force, in case our height changed, in which case we still
3540        // want to send the notification over to webkit
3541        setNewZoomScale(mActualScale, true);
3542    }
3543
3544    @Override
3545    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
3546        super.onScrollChanged(l, t, oldl, oldt);
3547        sendOurVisibleRect();
3548    }
3549
3550
3551    @Override
3552    public boolean dispatchKeyEvent(KeyEvent event) {
3553        boolean dispatch = true;
3554
3555        if (!inEditingMode()) {
3556            if (event.getAction() == KeyEvent.ACTION_DOWN) {
3557                mGotKeyDown = true;
3558            } else {
3559                if (!mGotKeyDown) {
3560                    /*
3561                     * We got a key up for which we were not the recipient of
3562                     * the original key down. Don't give it to the view.
3563                     */
3564                    dispatch = false;
3565                }
3566                mGotKeyDown = false;
3567            }
3568        }
3569
3570        if (dispatch) {
3571            return super.dispatchKeyEvent(event);
3572        } else {
3573            // We didn't dispatch, so let something else handle the key
3574            return false;
3575        }
3576    }
3577
3578    // Here are the snap align logic:
3579    // 1. If it starts nearly horizontally or vertically, snap align;
3580    // 2. If there is a dramitic direction change, let it go;
3581    // 3. If there is a same direction back and forth, lock it.
3582
3583    // adjustable parameters
3584    private int mMinLockSnapReverseDistance;
3585    private static final float MAX_SLOPE_FOR_DIAG = 1.5f;
3586    private static final int MIN_BREAK_SNAP_CROSS_DISTANCE = 80;
3587
3588    @Override
3589    public boolean onTouchEvent(MotionEvent ev) {
3590        if (mNativeClass == 0 || !isClickable() || !isLongClickable()) {
3591            return false;
3592        }
3593
3594        if (LOGV_ENABLED) {
3595            Log.v(LOGTAG, ev + " at " + ev.getEventTime() + " mTouchMode="
3596                    + mTouchMode);
3597        }
3598
3599        int action = ev.getAction();
3600        float x = ev.getX();
3601        float y = ev.getY();
3602        long eventTime = ev.getEventTime();
3603
3604        // Due to the touch screen edge effect, a touch closer to the edge
3605        // always snapped to the edge. As getViewWidth() can be different from
3606        // getWidth() due to the scrollbar, adjusting the point to match
3607        // getViewWidth(). Same applied to the height.
3608        if (x > getViewWidth() - 1) {
3609            x = getViewWidth() - 1;
3610        }
3611        if (y > getViewHeight() - 1) {
3612            y = getViewHeight() - 1;
3613        }
3614
3615        // pass the touch events from UI thread to WebCore thread
3616        if (mForwardTouchEvents && mTouchMode != SCROLL_ZOOM_OUT
3617                && mTouchMode != SCROLL_ZOOM_ANIMATION_IN
3618                && mTouchMode != SCROLL_ZOOM_ANIMATION_OUT
3619                && (action != MotionEvent.ACTION_MOVE ||
3620                        eventTime - mLastSentTouchTime > TOUCH_SENT_INTERVAL)) {
3621            WebViewCore.TouchEventData ted = new WebViewCore.TouchEventData();
3622            ted.mAction = action;
3623            ted.mX = viewToContent((int) x + mScrollX);
3624            ted.mY = viewToContent((int) y + mScrollY);
3625            mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
3626            mLastSentTouchTime = eventTime;
3627        }
3628
3629        int deltaX = (int) (mLastTouchX - x);
3630        int deltaY = (int) (mLastTouchY - y);
3631
3632        switch (action) {
3633            case MotionEvent.ACTION_DOWN: {
3634                if (mTouchMode == SCROLL_ZOOM_ANIMATION_IN
3635                        || mTouchMode == SCROLL_ZOOM_ANIMATION_OUT) {
3636                    // no interaction while animation is in progress
3637                    break;
3638                } else if (mTouchMode == SCROLL_ZOOM_OUT) {
3639                    mLastScrollX = mZoomScrollX;
3640                    mLastScrollY = mZoomScrollY;
3641                    // If two taps are close, ignore the first tap
3642                } else if (!mScroller.isFinished()) {
3643                    mScroller.abortAnimation();
3644                    mTouchMode = TOUCH_DRAG_START_MODE;
3645                    mPrivateHandler.removeMessages(RESUME_WEBCORE_UPDATE);
3646                } else if (mShiftIsPressed) {
3647                    mSelectX = mScrollX + (int) x;
3648                    mSelectY = mScrollY + (int) y;
3649                    mTouchMode = TOUCH_SELECT_MODE;
3650                    if (LOGV_ENABLED) {
3651                        Log.v(LOGTAG, "select=" + mSelectX + "," + mSelectY);
3652                    }
3653                    nativeMoveSelection(viewToContent(mSelectX)
3654                            , viewToContent(mSelectY), false);
3655                    mTouchSelection = mExtendSelection = true;
3656                } else {
3657                    mTouchMode = TOUCH_INIT_MODE;
3658                    mPreventDrag = mForwardTouchEvents;
3659                    if (mLogEvent && eventTime - mLastTouchUpTime < 1000) {
3660                        EventLog.writeEvent(EVENT_LOG_DOUBLE_TAP_DURATION,
3661                                (eventTime - mLastTouchUpTime), eventTime);
3662                    }
3663                }
3664                // Trigger the link
3665                if (mTouchMode == TOUCH_INIT_MODE) {
3666                    mPrivateHandler.sendMessageDelayed(mPrivateHandler
3667                            .obtainMessage(SWITCH_TO_SHORTPRESS), TAP_TIMEOUT);
3668                }
3669                // Remember where the motion event started
3670                mLastTouchX = x;
3671                mLastTouchY = y;
3672                mLastTouchTime = eventTime;
3673                mVelocityTracker = VelocityTracker.obtain();
3674                mSnapScrollMode = SNAP_NONE;
3675                break;
3676            }
3677            case MotionEvent.ACTION_MOVE: {
3678                if (mTouchMode == TOUCH_DONE_MODE
3679                        || mTouchMode == SCROLL_ZOOM_ANIMATION_IN
3680                        || mTouchMode == SCROLL_ZOOM_ANIMATION_OUT) {
3681                    // no dragging during scroll zoom animation
3682                    break;
3683                }
3684                if (mTouchMode == SCROLL_ZOOM_OUT) {
3685                    // while fully zoomed out, move the virtual window
3686                    moveZoomScrollWindow(x, y);
3687                    break;
3688                }
3689                mVelocityTracker.addMovement(ev);
3690
3691                if (mTouchMode != TOUCH_DRAG_MODE) {
3692                    if (mTouchMode == TOUCH_SELECT_MODE) {
3693                        mSelectX = mScrollX + (int) x;
3694                        mSelectY = mScrollY + (int) y;
3695                        if (LOGV_ENABLED) {
3696                            Log.v(LOGTAG, "xtend=" + mSelectX + "," + mSelectY);
3697                        }
3698                        nativeMoveSelection(viewToContent(mSelectX)
3699                                , viewToContent(mSelectY), true);
3700                        invalidate();
3701                        break;
3702                    }
3703                    if (mPreventDrag || (deltaX * deltaX + deltaY * deltaY)
3704                            < mTouchSlopSquare) {
3705                        break;
3706                    }
3707
3708                    if (mTouchMode == TOUCH_SHORTPRESS_MODE
3709                            || mTouchMode == TOUCH_SHORTPRESS_START_MODE) {
3710                        mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
3711                    } else if (mTouchMode == TOUCH_INIT_MODE) {
3712                        mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
3713                    }
3714
3715                    // if it starts nearly horizontal or vertical, enforce it
3716                    int ax = Math.abs(deltaX);
3717                    int ay = Math.abs(deltaY);
3718                    if (ax > MAX_SLOPE_FOR_DIAG * ay) {
3719                        mSnapScrollMode = SNAP_X;
3720                        mSnapPositive = deltaX > 0;
3721                    } else if (ay > MAX_SLOPE_FOR_DIAG * ax) {
3722                        mSnapScrollMode = SNAP_Y;
3723                        mSnapPositive = deltaY > 0;
3724                    }
3725
3726                    mTouchMode = TOUCH_DRAG_MODE;
3727                    WebViewCore.pauseUpdate(mWebViewCore);
3728                    int contentX = viewToContent((int) x + mScrollX);
3729                    int contentY = viewToContent((int) y + mScrollY);
3730                    nativeClearFocus(contentX, contentY);
3731                    // remove the zoom anchor if there is any
3732                    if (mZoomScale != 0) {
3733                        mWebViewCore
3734                                .sendMessage(EventHub.SET_SNAP_ANCHOR, 0, 0);
3735                    }
3736                    WebSettings settings = getSettings();
3737                    if (settings.supportZoom()
3738                            && settings.getBuiltInZoomControls()
3739                            && !mZoomButtonsController.isVisible()
3740                            && (canZoomScrollOut() ||
3741                                    mMinZoomScale < mMaxZoomScale)) {
3742                        mZoomButtonsController.setVisible(true);
3743                    }
3744                }
3745
3746                // do pan
3747                int newScrollX = pinLocX(mScrollX + deltaX);
3748                deltaX = newScrollX - mScrollX;
3749                int newScrollY = pinLocY(mScrollY + deltaY);
3750                deltaY = newScrollY - mScrollY;
3751                boolean done = false;
3752                if (deltaX == 0 && deltaY == 0) {
3753                    done = true;
3754                } else {
3755                    if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_Y) {
3756                        int ax = Math.abs(deltaX);
3757                        int ay = Math.abs(deltaY);
3758                        if (mSnapScrollMode == SNAP_X) {
3759                            // radical change means getting out of snap mode
3760                            if (ay > MAX_SLOPE_FOR_DIAG * ax
3761                                    && ay > MIN_BREAK_SNAP_CROSS_DISTANCE) {
3762                                mSnapScrollMode = SNAP_NONE;
3763                            }
3764                            // reverse direction means lock in the snap mode
3765                            if ((ax > MAX_SLOPE_FOR_DIAG * ay) &&
3766                                    ((mSnapPositive &&
3767                                    deltaX < -mMinLockSnapReverseDistance)
3768                                    || (!mSnapPositive &&
3769                                    deltaX > mMinLockSnapReverseDistance))) {
3770                                mSnapScrollMode = SNAP_X_LOCK;
3771                            }
3772                        } else {
3773                            // radical change means getting out of snap mode
3774                            if ((ax > MAX_SLOPE_FOR_DIAG * ay)
3775                                    && ax > MIN_BREAK_SNAP_CROSS_DISTANCE) {
3776                                mSnapScrollMode = SNAP_NONE;
3777                            }
3778                            // reverse direction means lock in the snap mode
3779                            if ((ay > MAX_SLOPE_FOR_DIAG * ax) &&
3780                                    ((mSnapPositive &&
3781                                    deltaY < -mMinLockSnapReverseDistance)
3782                                    || (!mSnapPositive &&
3783                                    deltaY > mMinLockSnapReverseDistance))) {
3784                                mSnapScrollMode = SNAP_Y_LOCK;
3785                            }
3786                        }
3787                    }
3788
3789                    if (mSnapScrollMode == SNAP_X
3790                            || mSnapScrollMode == SNAP_X_LOCK) {
3791                        scrollBy(deltaX, 0);
3792                        mLastTouchX = x;
3793                    } else if (mSnapScrollMode == SNAP_Y
3794                            || mSnapScrollMode == SNAP_Y_LOCK) {
3795                        scrollBy(0, deltaY);
3796                        mLastTouchY = y;
3797                    } else {
3798                        scrollBy(deltaX, deltaY);
3799                        mLastTouchX = x;
3800                        mLastTouchY = y;
3801                    }
3802                    mLastTouchTime = eventTime;
3803                    mUserScroll = true;
3804                }
3805
3806                if (!getSettings().getBuiltInZoomControls()) {
3807                    boolean showPlusMinus = mMinZoomScale < mMaxZoomScale;
3808                    boolean showMagnify = canZoomScrollOut();
3809                    if (mZoomControls != null && (showPlusMinus || showMagnify)) {
3810                        if (mZoomControls.getVisibility() == View.VISIBLE) {
3811                            mPrivateHandler.removeCallbacks(mZoomControlRunnable);
3812                        } else {
3813                            mZoomControls.show(showPlusMinus, showMagnify);
3814                        }
3815                        mPrivateHandler.postDelayed(mZoomControlRunnable,
3816                                ZOOM_CONTROLS_TIMEOUT);
3817                    }
3818                }
3819
3820                if (done) {
3821                    // return false to indicate that we can't pan out of the
3822                    // view space
3823                    return false;
3824                }
3825                break;
3826            }
3827            case MotionEvent.ACTION_UP: {
3828                mLastTouchUpTime = eventTime;
3829                switch (mTouchMode) {
3830                    case TOUCH_INIT_MODE: // tap
3831                    case TOUCH_SHORTPRESS_START_MODE:
3832                    case TOUCH_SHORTPRESS_MODE:
3833                        mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
3834                        mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
3835                        mTouchMode = TOUCH_DONE_MODE;
3836                        doShortPress();
3837                        break;
3838                    case TOUCH_SELECT_MODE:
3839                        commitCopy();
3840                        mTouchSelection = false;
3841                        break;
3842                    case SCROLL_ZOOM_ANIMATION_IN:
3843                    case SCROLL_ZOOM_ANIMATION_OUT:
3844                        // no action during scroll animation
3845                        break;
3846                    case SCROLL_ZOOM_OUT:
3847                        if (LOGV_ENABLED) {
3848                            Log.v(LOGTAG, "ACTION_UP SCROLL_ZOOM_OUT"
3849                                    + " eventTime - mLastTouchTime="
3850                                    + (eventTime - mLastTouchTime));
3851                        }
3852                        // for now, always zoom back when the drag completes
3853                        if (true || eventTime - mLastTouchTime < TAP_TIMEOUT) {
3854                            // but if we tap, zoom in where we tap
3855                            if (eventTime - mLastTouchTime < TAP_TIMEOUT) {
3856                                zoomScrollTap(x, y);
3857                            }
3858                            // start zooming in back to the original view
3859                            setZoomScrollIn();
3860                            mTouchMode = SCROLL_ZOOM_ANIMATION_IN;
3861                            invalidate();
3862                        }
3863                        break;
3864                    case TOUCH_DRAG_MODE:
3865                        // if the user waits a while w/o moving before the
3866                        // up, we don't want to do a fling
3867                        if (eventTime - mLastTouchTime <= MIN_FLING_TIME) {
3868                            mVelocityTracker.addMovement(ev);
3869                            doFling();
3870                            break;
3871                        }
3872                        WebViewCore.resumeUpdate(mWebViewCore);
3873                        break;
3874                    case TOUCH_DRAG_START_MODE:
3875                    case TOUCH_DONE_MODE:
3876                        // do nothing
3877                        break;
3878                }
3879                // we also use mVelocityTracker == null to tell us that we are
3880                // not "moving around", so we can take the slower/prettier
3881                // mode in the drawing code
3882                if (mVelocityTracker != null) {
3883                    mVelocityTracker.recycle();
3884                    mVelocityTracker = null;
3885                }
3886                break;
3887            }
3888            case MotionEvent.ACTION_CANCEL: {
3889                // we also use mVelocityTracker == null to tell us that we are
3890                // not "moving around", so we can take the slower/prettier
3891                // mode in the drawing code
3892                if (mVelocityTracker != null) {
3893                    mVelocityTracker.recycle();
3894                    mVelocityTracker = null;
3895                }
3896                if (mTouchMode == SCROLL_ZOOM_OUT ||
3897                        mTouchMode == SCROLL_ZOOM_ANIMATION_IN) {
3898                    scrollTo(mZoomScrollX, mZoomScrollY);
3899                } else if (mTouchMode == TOUCH_DRAG_MODE) {
3900                    WebViewCore.resumeUpdate(mWebViewCore);
3901                }
3902                mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
3903                mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
3904                mTouchMode = TOUCH_DONE_MODE;
3905                int contentX = viewToContent((int) mLastTouchX + mScrollX);
3906                int contentY = viewToContent((int) mLastTouchY + mScrollY);
3907                nativeClearFocus(contentX, contentY);
3908                break;
3909            }
3910        }
3911        return true;
3912    }
3913
3914    private long mTrackballFirstTime = 0;
3915    private long mTrackballLastTime = 0;
3916    private float mTrackballRemainsX = 0.0f;
3917    private float mTrackballRemainsY = 0.0f;
3918    private int mTrackballXMove = 0;
3919    private int mTrackballYMove = 0;
3920    private boolean mExtendSelection = false;
3921    private boolean mTouchSelection = false;
3922    private static final int TRACKBALL_KEY_TIMEOUT = 1000;
3923    private static final int TRACKBALL_TIMEOUT = 200;
3924    private static final int TRACKBALL_WAIT = 100;
3925    private static final int TRACKBALL_SCALE = 400;
3926    private static final int TRACKBALL_SCROLL_COUNT = 5;
3927    private static final int TRACKBALL_MOVE_COUNT = 10;
3928    private static final int TRACKBALL_MULTIPLIER = 3;
3929    private static final int SELECT_CURSOR_OFFSET = 16;
3930    private int mSelectX = 0;
3931    private int mSelectY = 0;
3932    private boolean mShiftIsPressed = false;
3933    private boolean mTrackballDown = false;
3934    private long mTrackballUpTime = 0;
3935    private long mLastFocusTime = 0;
3936    private Rect mLastFocusBounds;
3937
3938    // Set by default; BrowserActivity clears to interpret trackball data
3939    // directly for movement. Currently, the framework only passes
3940    // arrow key events, not trackball events, from one child to the next
3941    private boolean mMapTrackballToArrowKeys = true;
3942
3943    public void setMapTrackballToArrowKeys(boolean setMap) {
3944        mMapTrackballToArrowKeys = setMap;
3945    }
3946
3947    void resetTrackballTime() {
3948        mTrackballLastTime = 0;
3949    }
3950
3951    @Override
3952    public boolean onTrackballEvent(MotionEvent ev) {
3953        long time = ev.getEventTime();
3954        if ((ev.getMetaState() & KeyEvent.META_ALT_ON) != 0) {
3955            if (ev.getY() > 0) pageDown(true);
3956            if (ev.getY() < 0) pageUp(true);
3957            return true;
3958        }
3959        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
3960            mPrivateHandler.removeMessages(SWITCH_TO_ENTER);
3961            mTrackballDown = true;
3962            if (mNativeClass != 0) {
3963                nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true);
3964            }
3965            if (time - mLastFocusTime <= TRACKBALL_TIMEOUT
3966                    && !mLastFocusBounds.equals(nativeGetFocusRingBounds())) {
3967                nativeSelectBestAt(mLastFocusBounds);
3968            }
3969            if (LOGV_ENABLED) {
3970                Log.v(LOGTAG, "onTrackballEvent down ev=" + ev
3971                        + " time=" + time
3972                        + " mLastFocusTime=" + mLastFocusTime);
3973            }
3974            if (isInTouchMode()) requestFocusFromTouch();
3975            return false; // let common code in onKeyDown at it
3976        }
3977        if (ev.getAction() == MotionEvent.ACTION_UP) {
3978            // LONG_PRESS_ENTER is set in common onKeyDown
3979            mPrivateHandler.removeMessages(LONG_PRESS_ENTER);
3980            mTrackballDown = false;
3981            mTrackballUpTime = time;
3982            if (mShiftIsPressed) {
3983                if (mExtendSelection) {
3984                    commitCopy();
3985                } else {
3986                    mExtendSelection = true;
3987                }
3988            }
3989            if (LOGV_ENABLED) {
3990                Log.v(LOGTAG, "onTrackballEvent up ev=" + ev
3991                        + " time=" + time
3992                );
3993            }
3994            return false; // let common code in onKeyUp at it
3995        }
3996        if (mMapTrackballToArrowKeys && mShiftIsPressed == false) {
3997            if (LOGV_ENABLED) Log.v(LOGTAG, "onTrackballEvent gmail quit");
3998            return false;
3999        }
4000        // no move if we're still waiting on SWITCH_TO_ENTER timeout
4001        if (mTouchMode == TOUCH_DOUBLECLICK_MODE) {
4002            if (LOGV_ENABLED) Log.v(LOGTAG, "onTrackballEvent 2 click quit");
4003            return true;
4004        }
4005        if (mTrackballDown) {
4006            if (LOGV_ENABLED) Log.v(LOGTAG, "onTrackballEvent down quit");
4007            return true; // discard move if trackball is down
4008        }
4009        if (time - mTrackballUpTime < TRACKBALL_TIMEOUT) {
4010            if (LOGV_ENABLED) Log.v(LOGTAG, "onTrackballEvent up timeout quit");
4011            return true;
4012        }
4013        // TODO: alternatively we can do panning as touch does
4014        switchOutDrawHistory();
4015        if (time - mTrackballLastTime > TRACKBALL_TIMEOUT) {
4016            if (LOGV_ENABLED) {
4017                Log.v(LOGTAG, "onTrackballEvent time="
4018                        + time + " last=" + mTrackballLastTime);
4019            }
4020            mTrackballFirstTime = time;
4021            mTrackballXMove = mTrackballYMove = 0;
4022        }
4023        mTrackballLastTime = time;
4024        if (LOGV_ENABLED) {
4025            Log.v(LOGTAG, "onTrackballEvent ev=" + ev + " time=" + time);
4026        }
4027        mTrackballRemainsX += ev.getX();
4028        mTrackballRemainsY += ev.getY();
4029        doTrackball(time);
4030        return true;
4031    }
4032
4033    void moveSelection(float xRate, float yRate) {
4034        if (mNativeClass == 0)
4035            return;
4036        int width = getViewWidth();
4037        int height = getViewHeight();
4038        mSelectX += scaleTrackballX(xRate, width);
4039        mSelectY += scaleTrackballY(yRate, height);
4040        int maxX = width + mScrollX;
4041        int maxY = height + mScrollY;
4042        mSelectX = Math.min(maxX, Math.max(mScrollX - SELECT_CURSOR_OFFSET
4043                , mSelectX));
4044        mSelectY = Math.min(maxY, Math.max(mScrollY - SELECT_CURSOR_OFFSET
4045                , mSelectY));
4046        if (LOGV_ENABLED) {
4047            Log.v(LOGTAG, "moveSelection"
4048                    + " mSelectX=" + mSelectX
4049                    + " mSelectY=" + mSelectY
4050                    + " mScrollX=" + mScrollX
4051                    + " mScrollY=" + mScrollY
4052                    + " xRate=" + xRate
4053                    + " yRate=" + yRate
4054                    );
4055        }
4056        nativeMoveSelection(viewToContent(mSelectX)
4057                , viewToContent(mSelectY), mExtendSelection);
4058        int scrollX = mSelectX < mScrollX ? -SELECT_CURSOR_OFFSET
4059                : mSelectX > maxX - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET
4060                : 0;
4061        int scrollY = mSelectY < mScrollY ? -SELECT_CURSOR_OFFSET
4062                : mSelectY > maxY - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET
4063                : 0;
4064        pinScrollBy(scrollX, scrollY, true, 0);
4065        Rect select = new Rect(mSelectX, mSelectY, mSelectX + 1, mSelectY + 1);
4066        requestRectangleOnScreen(select);
4067        invalidate();
4068   }
4069
4070    private int scaleTrackballX(float xRate, int width) {
4071        int xMove = (int) (xRate / TRACKBALL_SCALE * width);
4072        int nextXMove = xMove;
4073        if (xMove > 0) {
4074            if (xMove > mTrackballXMove) {
4075                xMove -= mTrackballXMove;
4076            }
4077        } else if (xMove < mTrackballXMove) {
4078            xMove -= mTrackballXMove;
4079        }
4080        mTrackballXMove = nextXMove;
4081        return xMove;
4082    }
4083
4084    private int scaleTrackballY(float yRate, int height) {
4085        int yMove = (int) (yRate / TRACKBALL_SCALE * height);
4086        int nextYMove = yMove;
4087        if (yMove > 0) {
4088            if (yMove > mTrackballYMove) {
4089                yMove -= mTrackballYMove;
4090            }
4091        } else if (yMove < mTrackballYMove) {
4092            yMove -= mTrackballYMove;
4093        }
4094        mTrackballYMove = nextYMove;
4095        return yMove;
4096    }
4097
4098    private int keyCodeToSoundsEffect(int keyCode) {
4099        switch(keyCode) {
4100            case KeyEvent.KEYCODE_DPAD_UP:
4101                return SoundEffectConstants.NAVIGATION_UP;
4102            case KeyEvent.KEYCODE_DPAD_RIGHT:
4103                return SoundEffectConstants.NAVIGATION_RIGHT;
4104            case KeyEvent.KEYCODE_DPAD_DOWN:
4105                return SoundEffectConstants.NAVIGATION_DOWN;
4106            case KeyEvent.KEYCODE_DPAD_LEFT:
4107                return SoundEffectConstants.NAVIGATION_LEFT;
4108        }
4109        throw new IllegalArgumentException("keyCode must be one of " +
4110                "{KEYCODE_DPAD_UP, KEYCODE_DPAD_RIGHT, KEYCODE_DPAD_DOWN, " +
4111                "KEYCODE_DPAD_LEFT}.");
4112    }
4113
4114    private void doTrackball(long time) {
4115        int elapsed = (int) (mTrackballLastTime - mTrackballFirstTime);
4116        if (elapsed == 0) {
4117            elapsed = TRACKBALL_TIMEOUT;
4118        }
4119        float xRate = mTrackballRemainsX * 1000 / elapsed;
4120        float yRate = mTrackballRemainsY * 1000 / elapsed;
4121        if (mShiftIsPressed) {
4122            moveSelection(xRate, yRate);
4123            mTrackballRemainsX = mTrackballRemainsY = 0;
4124            return;
4125        }
4126        float ax = Math.abs(xRate);
4127        float ay = Math.abs(yRate);
4128        float maxA = Math.max(ax, ay);
4129        if (LOGV_ENABLED) {
4130            Log.v(LOGTAG, "doTrackball elapsed=" + elapsed
4131                    + " xRate=" + xRate
4132                    + " yRate=" + yRate
4133                    + " mTrackballRemainsX=" + mTrackballRemainsX
4134                    + " mTrackballRemainsY=" + mTrackballRemainsY);
4135        }
4136        int width = mContentWidth - getViewWidth();
4137        int height = mContentHeight - getViewHeight();
4138        if (width < 0) width = 0;
4139        if (height < 0) height = 0;
4140        if (mTouchMode == SCROLL_ZOOM_OUT) {
4141            int oldX = mZoomScrollX;
4142            int oldY = mZoomScrollY;
4143            int maxWH = Math.max(width, height);
4144            mZoomScrollX += scaleTrackballX(xRate, maxWH);
4145            mZoomScrollY += scaleTrackballY(yRate, maxWH);
4146            if (LOGV_ENABLED) {
4147                Log.v(LOGTAG, "doTrackball SCROLL_ZOOM_OUT"
4148                        + " mZoomScrollX=" + mZoomScrollX
4149                        + " mZoomScrollY=" + mZoomScrollY);
4150            }
4151            mZoomScrollX = Math.min(width, Math.max(0, mZoomScrollX));
4152            mZoomScrollY = Math.min(height, Math.max(0, mZoomScrollY));
4153            if (oldX != mZoomScrollX || oldY != mZoomScrollY) {
4154                invalidate();
4155            }
4156            mTrackballRemainsX = mTrackballRemainsY = 0;
4157            return;
4158        }
4159        ax = Math.abs(mTrackballRemainsX * TRACKBALL_MULTIPLIER);
4160        ay = Math.abs(mTrackballRemainsY * TRACKBALL_MULTIPLIER);
4161        maxA = Math.max(ax, ay);
4162        int count = Math.max(0, (int) maxA);
4163        int oldScrollX = mScrollX;
4164        int oldScrollY = mScrollY;
4165        if (count > 0) {
4166            int selectKeyCode = ax < ay ? mTrackballRemainsY < 0 ?
4167                    KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_DPAD_DOWN :
4168                    mTrackballRemainsX < 0 ? KeyEvent.KEYCODE_DPAD_LEFT :
4169                    KeyEvent.KEYCODE_DPAD_RIGHT;
4170            count = Math.min(count, TRACKBALL_MOVE_COUNT);
4171            if (LOGV_ENABLED) {
4172                Log.v(LOGTAG, "doTrackball keyCode=" + selectKeyCode
4173                        + " count=" + count
4174                        + " mTrackballRemainsX=" + mTrackballRemainsX
4175                        + " mTrackballRemainsY=" + mTrackballRemainsY);
4176            }
4177            if (navHandledKey(selectKeyCode, count, false, time)) {
4178                playSoundEffect(keyCodeToSoundsEffect(selectKeyCode));
4179            }
4180            mTrackballRemainsX = mTrackballRemainsY = 0;
4181        }
4182        if (count >= TRACKBALL_SCROLL_COUNT) {
4183            int xMove = scaleTrackballX(xRate, width);
4184            int yMove = scaleTrackballY(yRate, height);
4185            if (LOGV_ENABLED) {
4186                Log.v(LOGTAG, "doTrackball pinScrollBy"
4187                        + " count=" + count
4188                        + " xMove=" + xMove + " yMove=" + yMove
4189                        + " mScrollX-oldScrollX=" + (mScrollX-oldScrollX)
4190                        + " mScrollY-oldScrollY=" + (mScrollY-oldScrollY)
4191                        );
4192            }
4193            if (Math.abs(mScrollX - oldScrollX) > Math.abs(xMove)) {
4194                xMove = 0;
4195            }
4196            if (Math.abs(mScrollY - oldScrollY) > Math.abs(yMove)) {
4197                yMove = 0;
4198            }
4199            if (xMove != 0 || yMove != 0) {
4200                pinScrollBy(xMove, yMove, true, 0);
4201            }
4202            mUserScroll = true;
4203        }
4204        mWebViewCore.sendMessage(EventHub.UNBLOCK_FOCUS);
4205    }
4206
4207    public void flingScroll(int vx, int vy) {
4208        int maxX = Math.max(computeHorizontalScrollRange() - getViewWidth(), 0);
4209        int maxY = Math.max(computeVerticalScrollRange() - getViewHeight(), 0);
4210
4211        mScroller.fling(mScrollX, mScrollY, vx, vy, 0, maxX, 0, maxY);
4212        invalidate();
4213    }
4214
4215    private void doFling() {
4216        if (mVelocityTracker == null) {
4217            return;
4218        }
4219        int maxX = Math.max(computeHorizontalScrollRange() - getViewWidth(), 0);
4220        int maxY = Math.max(computeVerticalScrollRange() - getViewHeight(), 0);
4221
4222        mVelocityTracker.computeCurrentVelocity(1000);
4223        int vx = (int) mVelocityTracker.getXVelocity();
4224        int vy = (int) mVelocityTracker.getYVelocity();
4225
4226        if (mSnapScrollMode != SNAP_NONE) {
4227            if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_X_LOCK) {
4228                vy = 0;
4229            } else {
4230                vx = 0;
4231            }
4232        }
4233
4234        if (true /* EMG release: make our fling more like Maps' */) {
4235            // maps cuts their velocity in half
4236            vx = vx * 3 / 4;
4237            vy = vy * 3 / 4;
4238        }
4239
4240        mScroller.fling(mScrollX, mScrollY, -vx, -vy, 0, maxX, 0, maxY);
4241        // TODO: duration is calculated based on velocity, if the range is
4242        // small, the animation will stop before duration is up. We may
4243        // want to calculate how long the animation is going to run to precisely
4244        // resume the webcore update.
4245        final int time = mScroller.getDuration();
4246        mPrivateHandler.sendEmptyMessageDelayed(RESUME_WEBCORE_UPDATE, time);
4247        invalidate();
4248    }
4249
4250    private boolean zoomWithPreview(float scale) {
4251        float oldScale = mActualScale;
4252
4253        // snap to 100% if it is close
4254        if (scale > 0.95f && scale < 1.05f) {
4255            scale = 1.0f;
4256        }
4257
4258        setNewZoomScale(scale, false);
4259
4260        if (oldScale != mActualScale) {
4261            // use mZoomPickerScale to see zoom preview first
4262            mZoomStart = SystemClock.uptimeMillis();
4263            mInvInitialZoomScale = 1.0f / oldScale;
4264            mInvFinalZoomScale = 1.0f / mActualScale;
4265            mZoomScale = mActualScale;
4266            invalidate();
4267            return true;
4268        } else {
4269            return false;
4270        }
4271    }
4272
4273    /**
4274     * Returns a view containing zoom controls i.e. +/- buttons. The caller is
4275     * in charge of installing this view to the view hierarchy. This view will
4276     * become visible when the user starts scrolling via touch and fade away if
4277     * the user does not interact with it.
4278     * <p/>
4279     * API version 3 introduces a built-in zoom mechanism that is shown
4280     * automatically by the MapView. This is the preferred approach for
4281     * showing the zoom UI.
4282     *
4283     * @deprecated The built-in zoom mechanism is preferred, see
4284     *             {@link WebSettings#setBuiltInZoomControls(boolean)}.
4285     */
4286    @Deprecated
4287    public View getZoomControls() {
4288        if (!getSettings().supportZoom()) {
4289            Log.w(LOGTAG, "This WebView doesn't support zoom.");
4290            return null;
4291        }
4292        if (mZoomControls == null) {
4293            mZoomControls = createZoomControls();
4294
4295            /*
4296             * need to be set to VISIBLE first so that getMeasuredHeight() in
4297             * {@link #onSizeChanged()} can return the measured value for proper
4298             * layout.
4299             */
4300            mZoomControls.setVisibility(View.VISIBLE);
4301            mZoomControlRunnable = new Runnable() {
4302                public void run() {
4303
4304                    /* Don't dismiss the controls if the user has
4305                     * focus on them. Wait and check again later.
4306                     */
4307                    if (!mZoomControls.hasFocus()) {
4308                        mZoomControls.hide();
4309                    } else {
4310                        mPrivateHandler.removeCallbacks(mZoomControlRunnable);
4311                        mPrivateHandler.postDelayed(mZoomControlRunnable,
4312                                ZOOM_CONTROLS_TIMEOUT);
4313                    }
4314                }
4315            };
4316        }
4317        return mZoomControls;
4318    }
4319
4320    private ExtendedZoomControls createZoomControls() {
4321        ExtendedZoomControls zoomControls = new ExtendedZoomControls(mContext
4322            , null);
4323        zoomControls.setOnZoomInClickListener(new OnClickListener() {
4324            public void onClick(View v) {
4325                // reset time out
4326                mPrivateHandler.removeCallbacks(mZoomControlRunnable);
4327                mPrivateHandler.postDelayed(mZoomControlRunnable,
4328                        ZOOM_CONTROLS_TIMEOUT);
4329                zoomIn();
4330            }
4331        });
4332        zoomControls.setOnZoomOutClickListener(new OnClickListener() {
4333            public void onClick(View v) {
4334                // reset time out
4335                mPrivateHandler.removeCallbacks(mZoomControlRunnable);
4336                mPrivateHandler.postDelayed(mZoomControlRunnable,
4337                        ZOOM_CONTROLS_TIMEOUT);
4338                zoomOut();
4339            }
4340        });
4341        zoomControls.setOnZoomMagnifyClickListener(new OnClickListener() {
4342            public void onClick(View v) {
4343                mPrivateHandler.removeCallbacks(mZoomControlRunnable);
4344                mPrivateHandler.postDelayed(mZoomControlRunnable,
4345                        ZOOM_CONTROLS_TIMEOUT);
4346                zoomScrollOut();
4347            }
4348        });
4349        return zoomControls;
4350    }
4351
4352    /**
4353     * Gets the {@link ZoomButtonsController} which can be used to add
4354     * additional buttons to the zoom controls window.
4355     *
4356     * @return The instance of {@link ZoomButtonsController} used by this class,
4357     *         or null if it is unavailable.
4358     * @hide
4359     */
4360    public ZoomButtonsController getZoomButtonsController() {
4361        return mZoomButtonsController;
4362    }
4363
4364    /**
4365     * Perform zoom in in the webview
4366     * @return TRUE if zoom in succeeds. FALSE if no zoom changes.
4367     */
4368    public boolean zoomIn() {
4369        // TODO: alternatively we can disallow this during draw history mode
4370        switchOutDrawHistory();
4371        return zoomWithPreview(mActualScale * 1.25f);
4372    }
4373
4374    /**
4375     * Perform zoom out in the webview
4376     * @return TRUE if zoom out succeeds. FALSE if no zoom changes.
4377     */
4378    public boolean zoomOut() {
4379        // TODO: alternatively we can disallow this during draw history mode
4380        switchOutDrawHistory();
4381        return zoomWithPreview(mActualScale * 0.8f);
4382    }
4383
4384    private void updateSelection() {
4385        if (mNativeClass == 0) {
4386            return;
4387        }
4388        // mLastTouchX and mLastTouchY are the point in the current viewport
4389        int contentX = viewToContent((int) mLastTouchX + mScrollX);
4390        int contentY = viewToContent((int) mLastTouchY + mScrollY);
4391        Rect rect = new Rect(contentX - mNavSlop, contentY - mNavSlop,
4392                contentX + mNavSlop, contentY + mNavSlop);
4393        nativeSelectBestAt(rect);
4394    }
4395
4396    /*package*/ void shortPressOnTextField() {
4397        if (inEditingMode()) {
4398            View v = mTextEntry;
4399            int x = viewToContent((v.getLeft() + v.getRight()) >> 1);
4400            int y = viewToContent((v.getTop() + v.getBottom()) >> 1);
4401            nativeMotionUp(x, y, mNavSlop);
4402        }
4403    }
4404
4405    private void doShortPress() {
4406        if (mNativeClass == 0) {
4407            return;
4408        }
4409        switchOutDrawHistory();
4410        // mLastTouchX and mLastTouchY are the point in the current viewport
4411        int contentX = viewToContent((int) mLastTouchX + mScrollX);
4412        int contentY = viewToContent((int) mLastTouchY + mScrollY);
4413        if (nativeMotionUp(contentX, contentY, mNavSlop)) {
4414            if (mLogEvent) {
4415                Checkin.updateStats(mContext.getContentResolver(),
4416                        Checkin.Stats.Tag.BROWSER_SNAP_CENTER, 1, 0.0);
4417            }
4418        }
4419        if (nativeUpdateFocusNode() && !mFocusNode.mIsTextField
4420                && !mFocusNode.mIsTextArea) {
4421            playSoundEffect(SoundEffectConstants.CLICK);
4422        }
4423    }
4424
4425    // Called by JNI to handle a touch on a node representing an email address,
4426    // address, or phone number
4427    private void overrideLoading(String url) {
4428        mCallbackProxy.uiOverrideUrlLoading(url);
4429    }
4430
4431    @Override
4432    public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
4433        boolean result = false;
4434        if (inEditingMode()) {
4435            result = mTextEntry.requestFocus(direction, previouslyFocusedRect);
4436        } else {
4437            result = super.requestFocus(direction, previouslyFocusedRect);
4438            if (mWebViewCore.getSettings().getNeedInitialFocus()) {
4439                // For cases such as GMail, where we gain focus from a direction,
4440                // we want to move to the first available link.
4441                // FIXME: If there are no visible links, we may not want to
4442                int fakeKeyDirection = 0;
4443                switch(direction) {
4444                    case View.FOCUS_UP:
4445                        fakeKeyDirection = KeyEvent.KEYCODE_DPAD_UP;
4446                        break;
4447                    case View.FOCUS_DOWN:
4448                        fakeKeyDirection = KeyEvent.KEYCODE_DPAD_DOWN;
4449                        break;
4450                    case View.FOCUS_LEFT:
4451                        fakeKeyDirection = KeyEvent.KEYCODE_DPAD_LEFT;
4452                        break;
4453                    case View.FOCUS_RIGHT:
4454                        fakeKeyDirection = KeyEvent.KEYCODE_DPAD_RIGHT;
4455                        break;
4456                    default:
4457                        return result;
4458                }
4459                if (mNativeClass != 0 && !nativeUpdateFocusNode()) {
4460                    navHandledKey(fakeKeyDirection, 1, true, 0);
4461                }
4462            }
4463        }
4464        return result;
4465    }
4466
4467    @Override
4468    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
4469        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
4470
4471        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
4472        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
4473        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
4474        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
4475
4476        int measuredHeight = heightSize;
4477        int measuredWidth = widthSize;
4478
4479        // Grab the content size from WebViewCore.
4480        int contentHeight = mContentHeight;
4481        int contentWidth = mContentWidth;
4482
4483//        Log.d(LOGTAG, "------- measure " + heightMode);
4484
4485        if (heightMode != MeasureSpec.EXACTLY) {
4486            mHeightCanMeasure = true;
4487            measuredHeight = contentHeight;
4488            if (heightMode == MeasureSpec.AT_MOST) {
4489                // If we are larger than the AT_MOST height, then our height can
4490                // no longer be measured and we should scroll internally.
4491                if (measuredHeight > heightSize) {
4492                    measuredHeight = heightSize;
4493                    mHeightCanMeasure = false;
4494                }
4495            }
4496        } else {
4497            mHeightCanMeasure = false;
4498        }
4499        if (mNativeClass != 0) {
4500            nativeSetHeightCanMeasure(mHeightCanMeasure);
4501        }
4502        // For the width, always use the given size unless unspecified.
4503        if (widthMode == MeasureSpec.UNSPECIFIED) {
4504            mWidthCanMeasure = true;
4505            measuredWidth = contentWidth;
4506        } else {
4507            mWidthCanMeasure = false;
4508        }
4509
4510        synchronized (this) {
4511            setMeasuredDimension(measuredWidth, measuredHeight);
4512        }
4513    }
4514
4515    @Override
4516    public boolean requestChildRectangleOnScreen(View child,
4517                                                 Rect rect,
4518                                                 boolean immediate) {
4519        rect.offset(child.getLeft() - child.getScrollX(),
4520                child.getTop() - child.getScrollY());
4521
4522        int height = getHeight() - getHorizontalScrollbarHeight();
4523        int screenTop = mScrollY;
4524        int screenBottom = screenTop + height;
4525
4526        int scrollYDelta = 0;
4527
4528        if (rect.bottom > screenBottom) {
4529            int oneThirdOfScreenHeight = height / 3;
4530            if (rect.height() > 2 * oneThirdOfScreenHeight) {
4531                // If the rectangle is too tall to fit in the bottom two thirds
4532                // of the screen, place it at the top.
4533                scrollYDelta = rect.top - screenTop;
4534            } else {
4535                // If the rectangle will still fit on screen, we want its
4536                // top to be in the top third of the screen.
4537                scrollYDelta = rect.top - (screenTop + oneThirdOfScreenHeight);
4538            }
4539        } else if (rect.top < screenTop) {
4540            scrollYDelta = rect.top - screenTop;
4541        }
4542
4543        int width = getWidth() - getVerticalScrollbarWidth();
4544        int screenLeft = mScrollX;
4545        int screenRight = screenLeft + width;
4546
4547        int scrollXDelta = 0;
4548
4549        if (rect.right > screenRight && rect.left > screenLeft) {
4550            if (rect.width() > width) {
4551                scrollXDelta += (rect.left - screenLeft);
4552            } else {
4553                scrollXDelta += (rect.right - screenRight);
4554            }
4555        } else if (rect.left < screenLeft) {
4556            scrollXDelta -= (screenLeft - rect.left);
4557        }
4558
4559        if ((scrollYDelta | scrollXDelta) != 0) {
4560            return pinScrollBy(scrollXDelta, scrollYDelta, !immediate, 0);
4561        }
4562
4563        return false;
4564    }
4565
4566    /* package */ void replaceTextfieldText(int oldStart, int oldEnd,
4567            String replace, int newStart, int newEnd) {
4568        HashMap arg = new HashMap();
4569        arg.put("focusData", new WebViewCore.FocusData(mFocusData));
4570        arg.put("replace", replace);
4571        arg.put("start", new Integer(newStart));
4572        arg.put("end", new Integer(newEnd));
4573        mTextGeneration++;
4574        mWebViewCore.sendMessage(EventHub.REPLACE_TEXT, oldStart, oldEnd, arg);
4575    }
4576
4577    /* package */ void passToJavaScript(String currentText, KeyEvent event) {
4578        HashMap arg = new HashMap();
4579        arg.put("focusData", new WebViewCore.FocusData(mFocusData));
4580        arg.put("event", event);
4581        arg.put("currentText", currentText);
4582        // Increase our text generation number, and pass it to webcore thread
4583        mTextGeneration++;
4584        mWebViewCore.sendMessage(EventHub.PASS_TO_JS, mTextGeneration, 0, arg);
4585        // WebKit's document state is not saved until about to leave the page.
4586        // To make sure the host application, like Browser, has the up to date
4587        // document state when it goes to background, we force to save the
4588        // document state.
4589        mWebViewCore.removeMessages(EventHub.SAVE_DOCUMENT_STATE);
4590        mWebViewCore.sendMessageDelayed(EventHub.SAVE_DOCUMENT_STATE,
4591                new WebViewCore.FocusData(mFocusData), 1000);
4592    }
4593
4594    /* package */ WebViewCore getWebViewCore() {
4595        return mWebViewCore;
4596    }
4597
4598    //-------------------------------------------------------------------------
4599    // Methods can be called from a separate thread, like WebViewCore
4600    // If it needs to call the View system, it has to send message.
4601    //-------------------------------------------------------------------------
4602
4603    /**
4604     * General handler to receive message coming from webkit thread
4605     */
4606    class PrivateHandler extends Handler {
4607        @Override
4608        public void handleMessage(Message msg) {
4609            if (LOGV_ENABLED) {
4610                Log.v(LOGTAG, msg.what < REMEMBER_PASSWORD || msg.what
4611                        > INVAL_RECT_MSG_ID ? Integer.toString(msg.what)
4612                        : HandlerDebugString[msg.what - REMEMBER_PASSWORD]);
4613            }
4614            switch (msg.what) {
4615                case REMEMBER_PASSWORD: {
4616                    mDatabase.setUsernamePassword(
4617                            msg.getData().getString("host"),
4618                            msg.getData().getString("username"),
4619                            msg.getData().getString("password"));
4620                    ((Message) msg.obj).sendToTarget();
4621                    break;
4622                }
4623                case NEVER_REMEMBER_PASSWORD: {
4624                    mDatabase.setUsernamePassword(
4625                            msg.getData().getString("host"), null, null);
4626                    ((Message) msg.obj).sendToTarget();
4627                    break;
4628                }
4629                case SWITCH_TO_SHORTPRESS: {
4630                    if (mTouchMode == TOUCH_INIT_MODE) {
4631                        mTouchMode = TOUCH_SHORTPRESS_START_MODE;
4632                        updateSelection();
4633                    }
4634                    break;
4635                }
4636                case SWITCH_TO_LONGPRESS: {
4637                    mTouchMode = TOUCH_DONE_MODE;
4638                    performLongClick();
4639                    updateTextEntry();
4640                    break;
4641                }
4642                case SWITCH_TO_ENTER:
4643                    if (LOGV_ENABLED) Log.v(LOGTAG, "SWITCH_TO_ENTER");
4644                    mTouchMode = TOUCH_DONE_MODE;
4645                    onKeyUp(KeyEvent.KEYCODE_ENTER
4646                            , new KeyEvent(KeyEvent.ACTION_UP
4647                            , KeyEvent.KEYCODE_ENTER));
4648                    break;
4649                case SCROLL_BY_MSG_ID:
4650                    setContentScrollBy(msg.arg1, msg.arg2, (Boolean) msg.obj);
4651                    break;
4652                case SYNC_SCROLL_TO_MSG_ID:
4653                    if (mUserScroll) {
4654                        // if user has scrolled explicitly, don't sync the
4655                        // scroll position any more
4656                        mUserScroll = false;
4657                        break;
4658                    }
4659                    // fall through
4660                case SCROLL_TO_MSG_ID:
4661                    if (setContentScrollTo(msg.arg1, msg.arg2)) {
4662                        // if we can't scroll to the exact position due to pin,
4663                        // send a message to WebCore to re-scroll when we get a
4664                        // new picture
4665                        mUserScroll = false;
4666                        mWebViewCore.sendMessage(EventHub.SYNC_SCROLL,
4667                                msg.arg1, msg.arg2);
4668                    }
4669                    break;
4670                case SPAWN_SCROLL_TO_MSG_ID:
4671                    spawnContentScrollTo(msg.arg1, msg.arg2);
4672                    break;
4673                case NEW_PICTURE_MSG_ID:
4674                    // called for new content
4675                    final WebViewCore.DrawData draw =
4676                            (WebViewCore.DrawData) msg.obj;
4677                    final Point viewSize = draw.mViewPoint;
4678                    if (mZoomScale > 0) {
4679                        // use the same logic in sendViewSizeZoom() to make sure
4680                        // the mZoomScale has matched the viewSize so that we
4681                        // can clear mZoomScale
4682                        if (Math.round(getViewWidth() / mZoomScale) == viewSize.x) {
4683                            mZoomScale = 0;
4684                            mWebViewCore.sendMessage(EventHub.SET_SNAP_ANCHOR,
4685                                    0, 0);
4686                        }
4687                    }
4688                    if (!mMinZoomScaleFixed) {
4689                        mMinZoomScale = (float) getViewWidth()
4690                                / Math.max(ZOOM_OUT_WIDTH, draw.mWidthHeight.x);
4691                    }
4692                    // We update the layout (i.e. request a layout from the
4693                    // view system) if the last view size that we sent to
4694                    // WebCore matches the view size of the picture we just
4695                    // received in the fixed dimension.
4696                    final boolean updateLayout = viewSize.x == mLastWidthSent
4697                            && viewSize.y == mLastHeightSent;
4698                    recordNewContentSize(draw.mWidthHeight.x,
4699                            draw.mWidthHeight.y, updateLayout);
4700                    if (LOGV_ENABLED) {
4701                        Rect b = draw.mInvalRegion.getBounds();
4702                        Log.v(LOGTAG, "NEW_PICTURE_MSG_ID {" +
4703                                b.left+","+b.top+","+b.right+","+b.bottom+"}");
4704                    }
4705                    invalidate(contentToView(draw.mInvalRegion.getBounds()));
4706                    if (mPictureListener != null) {
4707                        mPictureListener.onNewPicture(WebView.this, capturePicture());
4708                    }
4709                    break;
4710                case WEBCORE_INITIALIZED_MSG_ID:
4711                    // nativeCreate sets mNativeClass to a non-zero value
4712                    nativeCreate(msg.arg1);
4713                    break;
4714                case UPDATE_TEXTFIELD_TEXT_MSG_ID:
4715                    // Make sure that the textfield is currently focused
4716                    // and representing the same node as the pointer.
4717                    if (inEditingMode() &&
4718                            mTextEntry.isSameTextField(msg.arg1)) {
4719                        if (msg.getData().getBoolean("password")) {
4720                            Spannable text = (Spannable) mTextEntry.getText();
4721                            int start = Selection.getSelectionStart(text);
4722                            int end = Selection.getSelectionEnd(text);
4723                            mTextEntry.setInPassword(true);
4724                            // Restore the selection, which may have been
4725                            // ruined by setInPassword.
4726                            Spannable pword = (Spannable) mTextEntry.getText();
4727                            Selection.setSelection(pword, start, end);
4728                        // If the text entry has created more events, ignore
4729                        // this one.
4730                        } else if (msg.arg2 == mTextGeneration) {
4731                            mTextEntry.setTextAndKeepSelection(
4732                                    (String) msg.obj);
4733                        }
4734                    }
4735                    break;
4736                case DID_FIRST_LAYOUT_MSG_ID:
4737                    if (mNativeClass == 0) {
4738                        break;
4739                    }
4740// Do not reset the focus or clear the text; the user may have already
4741// navigated or entered text at this point. The focus should have gotten
4742// reset, if need be, when the focus cache was built. Similarly, the text
4743// view should already be torn down and rebuilt if needed.
4744//                    nativeResetFocus();
4745//                    clearTextEntry();
4746                    HashMap scaleLimit = (HashMap) msg.obj;
4747                    int minScale = (Integer) scaleLimit.get("minScale");
4748                    if (minScale == 0) {
4749                        mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE;
4750                        mMinZoomScaleFixed = false;
4751                    } else {
4752                        mMinZoomScale = (float) (minScale / 100.0);
4753                        mMinZoomScaleFixed = true;
4754                    }
4755                    int maxScale = (Integer) scaleLimit.get("maxScale");
4756                    if (maxScale == 0) {
4757                        mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE;
4758                    } else {
4759                        mMaxZoomScale = (float) (maxScale / 100.0);
4760                    }
4761                    // If history Picture is drawn, don't update zoomWidth
4762                    if (mDrawHistory) {
4763                        break;
4764                    }
4765                    int width = getViewWidth();
4766                    if (width == 0) {
4767                        break;
4768                    }
4769                    int initialScale = msg.arg1;
4770                    int viewportWidth = msg.arg2;
4771                    // by default starting a new page with 100% zoom scale.
4772                    float scale = 1.0f;
4773                    if (mInitialScale > 0) {
4774                        scale = mInitialScale / 100.0f;
4775                    } else  {
4776                        if (initialScale < 0) break;
4777                        if (mWebViewCore.getSettings().getUseWideViewPort()) {
4778                            // force viewSizeChanged by setting mLastWidthSent
4779                            // to 0
4780                            mLastWidthSent = 0;
4781                        }
4782                        if (initialScale == 0) {
4783                            // if viewportWidth is defined and it is smaller
4784                            // than the view width, zoom in to fill the view
4785                            if (viewportWidth > 0 && viewportWidth < width) {
4786                                scale = (float) width / viewportWidth;
4787                            }
4788                        } else {
4789                            scale = initialScale / 100.0f;
4790                        }
4791                    }
4792                    setNewZoomScale(scale, false);
4793                    break;
4794                case MARK_NODE_INVALID_ID:
4795                    nativeMarkNodeInvalid(msg.arg1);
4796                    break;
4797                case NOTIFY_FOCUS_SET_MSG_ID:
4798                    if (mNativeClass != 0) {
4799                        nativeNotifyFocusSet(inEditingMode());
4800                    }
4801                    break;
4802                case UPDATE_TEXT_ENTRY_MSG_ID:
4803                    // this is sent after finishing resize in WebViewCore. Make
4804                    // sure the text edit box is still on the  screen.
4805                    boolean alreadyThere = inEditingMode();
4806                    if (alreadyThere && nativeUpdateFocusNode()) {
4807                        FocusNode node = mFocusNode;
4808                        if (node.mIsTextField || node.mIsTextArea) {
4809                            mTextEntry.bringIntoView();
4810                        }
4811                    }
4812                    updateTextEntry();
4813                    break;
4814                case RECOMPUTE_FOCUS_MSG_ID:
4815                    if (mNativeClass != 0) {
4816                        nativeRecomputeFocus();
4817                    }
4818                    break;
4819                case INVAL_RECT_MSG_ID: {
4820                    Rect r = (Rect)msg.obj;
4821                    if (r == null) {
4822                        invalidate();
4823                    } else {
4824                        // we need to scale r from content into view coords,
4825                        // which viewInvalidate() does for us
4826                        viewInvalidate(r.left, r.top, r.right, r.bottom);
4827                    }
4828                    break;
4829                }
4830                case UPDATE_TEXT_ENTRY_ADAPTER:
4831                    HashMap data = (HashMap) msg.obj;
4832                    if (mTextEntry.isSameTextField(msg.arg1)) {
4833                        AutoCompleteAdapter adapter =
4834                                (AutoCompleteAdapter) data.get("adapter");
4835                        mTextEntry.setAdapterCustom(adapter);
4836                    }
4837                    break;
4838                case UPDATE_CLIPBOARD:
4839                    String str = (String) msg.obj;
4840                    if (LOGV_ENABLED) {
4841                        Log.v(LOGTAG, "UPDATE_CLIPBOARD " + str);
4842                    }
4843                    try {
4844                        IClipboard clip = IClipboard.Stub.asInterface(
4845                                ServiceManager.getService("clipboard"));
4846                                clip.setClipboardText(str);
4847                    } catch (android.os.RemoteException e) {
4848                        Log.e(LOGTAG, "Clipboard failed", e);
4849                    }
4850                    break;
4851                case RESUME_WEBCORE_UPDATE:
4852                    WebViewCore.resumeUpdate(mWebViewCore);
4853                    break;
4854
4855                case LONG_PRESS_ENTER:
4856                    // as this is shared by keydown and trackballdown, reset all
4857                    // the states
4858                    mGotEnterDown = false;
4859                    mTrackballDown = false;
4860                    // LONG_PRESS_ENTER is sent as a delayed message. If we
4861                    // switch to windows overview, the WebView will be
4862                    // temporarily removed from the view system. In that case,
4863                    // do nothing.
4864                    if (getParent() != null) {
4865                        performLongClick();
4866                    }
4867                    break;
4868
4869                case WEBCORE_NEED_TOUCH_EVENTS:
4870                    mForwardTouchEvents = (msg.arg1 != 0);
4871                    break;
4872
4873                case PREVENT_TOUCH_ID:
4874                    if (msg.arg1 == MotionEvent.ACTION_DOWN) {
4875                        mPreventDrag = msg.arg2 == 1;
4876                        if (mPreventDrag) {
4877                            mTouchMode = TOUCH_DONE_MODE;
4878                        }
4879                    }
4880                    break;
4881
4882                default:
4883                    super.handleMessage(msg);
4884                    break;
4885            }
4886        }
4887    }
4888
4889    // Class used to use a dropdown for a <select> element
4890    private class InvokeListBox implements Runnable {
4891        // Strings for the labels in the listbox.
4892        private String[]    mArray;
4893        // Array representing whether each item is enabled.
4894        private boolean[]   mEnableArray;
4895        // Whether the listbox allows multiple selection.
4896        private boolean     mMultiple;
4897        // Passed in to a list with multiple selection to tell
4898        // which items are selected.
4899        private int[]       mSelectedArray;
4900        // Passed in to a list with single selection to tell
4901        // where the initial selection is.
4902        private int         mSelection;
4903
4904        private Container[] mContainers;
4905
4906        // Need these to provide stable ids to my ArrayAdapter,
4907        // which normally does not have stable ids. (Bug 1250098)
4908        private class Container extends Object {
4909            String  mString;
4910            boolean mEnabled;
4911            int     mId;
4912
4913            public String toString() {
4914                return mString;
4915            }
4916        }
4917
4918        /**
4919         *  Subclass ArrayAdapter so we can disable OptionGroupLabels,
4920         *  and allow filtering.
4921         */
4922        private class MyArrayListAdapter extends ArrayAdapter<Container> {
4923            public MyArrayListAdapter(Context context, Container[] objects, boolean multiple) {
4924                super(context,
4925                            multiple ? com.android.internal.R.layout.select_dialog_multichoice :
4926                            com.android.internal.R.layout.select_dialog_singlechoice,
4927                            objects);
4928            }
4929
4930            @Override
4931            public boolean hasStableIds() {
4932                // AdapterView's onChanged method uses this to determine whether
4933                // to restore the old state.  Return false so that the old (out
4934                // of date) state does not replace the new, valid state.
4935                return false;
4936            }
4937
4938            private Container item(int position) {
4939                if (position < 0 || position >= getCount()) {
4940                    return null;
4941                }
4942                return (Container) getItem(position);
4943            }
4944
4945            @Override
4946            public long getItemId(int position) {
4947                Container item = item(position);
4948                if (item == null) {
4949                    return -1;
4950                }
4951                return item.mId;
4952            }
4953
4954            @Override
4955            public boolean areAllItemsEnabled() {
4956                return false;
4957            }
4958
4959            @Override
4960            public boolean isEnabled(int position) {
4961                Container item = item(position);
4962                if (item == null) {
4963                    return false;
4964                }
4965                return item.mEnabled;
4966            }
4967        }
4968
4969        private InvokeListBox(String[] array,
4970                boolean[] enabled, int[] selected) {
4971            mMultiple = true;
4972            mSelectedArray = selected;
4973
4974            int length = array.length;
4975            mContainers = new Container[length];
4976            for (int i = 0; i < length; i++) {
4977                mContainers[i] = new Container();
4978                mContainers[i].mString = array[i];
4979                mContainers[i].mEnabled = enabled[i];
4980                mContainers[i].mId = i;
4981            }
4982        }
4983
4984        private InvokeListBox(String[] array, boolean[] enabled, int
4985                selection) {
4986            mSelection = selection;
4987            mMultiple = false;
4988
4989            int length = array.length;
4990            mContainers = new Container[length];
4991            for (int i = 0; i < length; i++) {
4992                mContainers[i] = new Container();
4993                mContainers[i].mString = array[i];
4994                mContainers[i].mEnabled = enabled[i];
4995                mContainers[i].mId = i;
4996            }
4997        }
4998
4999        /*
5000         * Whenever the data set changes due to filtering, this class ensures
5001         * that the checked item remains checked.
5002         */
5003        private class SingleDataSetObserver extends DataSetObserver {
5004            private long        mCheckedId;
5005            private ListView    mListView;
5006            private Adapter     mAdapter;
5007
5008            /*
5009             * Create a new observer.
5010             * @param id The ID of the item to keep checked.
5011             * @param l ListView for getting and clearing the checked states
5012             * @param a Adapter for getting the IDs
5013             */
5014            public SingleDataSetObserver(long id, ListView l, Adapter a) {
5015                mCheckedId = id;
5016                mListView = l;
5017                mAdapter = a;
5018            }
5019
5020            public void onChanged() {
5021                // The filter may have changed which item is checked.  Find the
5022                // item that the ListView thinks is checked.
5023                int position = mListView.getCheckedItemPosition();
5024                long id = mAdapter.getItemId(position);
5025                if (mCheckedId != id) {
5026                    // Clear the ListView's idea of the checked item, since
5027                    // it is incorrect
5028                    mListView.clearChoices();
5029                    // Search for mCheckedId.  If it is in the filtered list,
5030                    // mark it as checked
5031                    int count = mAdapter.getCount();
5032                    for (int i = 0; i < count; i++) {
5033                        if (mAdapter.getItemId(i) == mCheckedId) {
5034                            mListView.setItemChecked(i, true);
5035                            break;
5036                        }
5037                    }
5038                }
5039            }
5040
5041            public void onInvalidate() {}
5042        }
5043
5044        public void run() {
5045            final ListView listView = (ListView) LayoutInflater.from(mContext)
5046                    .inflate(com.android.internal.R.layout.select_dialog, null);
5047            final MyArrayListAdapter adapter = new
5048                    MyArrayListAdapter(mContext, mContainers, mMultiple);
5049            AlertDialog.Builder b = new AlertDialog.Builder(mContext)
5050                    .setView(listView).setCancelable(true)
5051                    .setInverseBackgroundForced(true);
5052
5053            if (mMultiple) {
5054                b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
5055                    public void onClick(DialogInterface dialog, int which) {
5056                        mWebViewCore.sendMessage(
5057                                EventHub.LISTBOX_CHOICES,
5058                                adapter.getCount(), 0,
5059                                listView.getCheckedItemPositions());
5060                    }});
5061                b.setNegativeButton(android.R.string.cancel,
5062                        new DialogInterface.OnClickListener() {
5063                    public void onClick(DialogInterface dialog, int which) {
5064                        mWebViewCore.sendMessage(
5065                                EventHub.SINGLE_LISTBOX_CHOICE, -2, 0);
5066                }});
5067            }
5068            final AlertDialog dialog = b.create();
5069            listView.setAdapter(adapter);
5070            listView.setFocusableInTouchMode(true);
5071            // There is a bug (1250103) where the checks in a ListView with
5072            // multiple items selected are associated with the positions, not
5073            // the ids, so the items do not properly retain their checks when
5074            // filtered.  Do not allow filtering on multiple lists until
5075            // that bug is fixed.
5076
5077            listView.setTextFilterEnabled(!mMultiple);
5078            if (mMultiple) {
5079                listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
5080                int length = mSelectedArray.length;
5081                for (int i = 0; i < length; i++) {
5082                    listView.setItemChecked(mSelectedArray[i], true);
5083                }
5084            } else {
5085                listView.setOnItemClickListener(new OnItemClickListener() {
5086                    public void onItemClick(AdapterView parent, View v,
5087                            int position, long id) {
5088                        mWebViewCore.sendMessage(
5089                                EventHub.SINGLE_LISTBOX_CHOICE, (int)id, 0);
5090                        dialog.dismiss();
5091                    }
5092                });
5093                if (mSelection != -1) {
5094                    listView.setSelection(mSelection);
5095                    listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
5096                    listView.setItemChecked(mSelection, true);
5097                    DataSetObserver observer = new SingleDataSetObserver(
5098                            adapter.getItemId(mSelection), listView, adapter);
5099                    adapter.registerDataSetObserver(observer);
5100                }
5101            }
5102            dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
5103                public void onCancel(DialogInterface dialog) {
5104                    mWebViewCore.sendMessage(
5105                                EventHub.SINGLE_LISTBOX_CHOICE, -2, 0);
5106                }
5107            });
5108            dialog.show();
5109        }
5110    }
5111
5112    /*
5113     * Request a dropdown menu for a listbox with multiple selection.
5114     *
5115     * @param array Labels for the listbox.
5116     * @param enabledArray  Which positions are enabled.
5117     * @param selectedArray Which positions are initally selected.
5118     */
5119    void requestListBox(String[] array, boolean[]enabledArray, int[]
5120            selectedArray) {
5121        mPrivateHandler.post(
5122                new InvokeListBox(array, enabledArray, selectedArray));
5123    }
5124
5125    /*
5126     * Request a dropdown menu for a listbox with single selection or a single
5127     * <select> element.
5128     *
5129     * @param array Labels for the listbox.
5130     * @param enabledArray  Which positions are enabled.
5131     * @param selection Which position is initally selected.
5132     */
5133    void requestListBox(String[] array, boolean[]enabledArray, int selection) {
5134        mPrivateHandler.post(
5135                new InvokeListBox(array, enabledArray, selection));
5136    }
5137
5138    // called by JNI
5139    private void sendFinalFocus(int frame, int node, int x, int y) {
5140        WebViewCore.FocusData focusData = new WebViewCore.FocusData();
5141        focusData.mFrame = frame;
5142        focusData.mNode = node;
5143        focusData.mX = x;
5144        focusData.mY = y;
5145        mWebViewCore.sendMessage(EventHub.SET_FINAL_FOCUS,
5146                EventHub.NO_FOCUS_CHANGE_BLOCK, 0, focusData);
5147    }
5148
5149    // called by JNI
5150    private void setFocusData(int moveGeneration, int buildGeneration,
5151            int frame, int node, int x, int y, boolean ignoreNullFocus) {
5152        mFocusData.mMoveGeneration = moveGeneration;
5153        mFocusData.mBuildGeneration = buildGeneration;
5154        mFocusData.mFrame = frame;
5155        mFocusData.mNode = node;
5156        mFocusData.mX = x;
5157        mFocusData.mY = y;
5158        mFocusData.mIgnoreNullFocus = ignoreNullFocus;
5159    }
5160
5161    // called by JNI
5162    private void sendKitFocus() {
5163        WebViewCore.FocusData focusData = new WebViewCore.FocusData(mFocusData);
5164        mWebViewCore.sendMessage(EventHub.SET_KIT_FOCUS, focusData);
5165    }
5166
5167    // called by JNI
5168    private void sendMotionUp(int touchGeneration, int buildGeneration,
5169            int frame, int node, int x, int y, int size,
5170            boolean retry) {
5171        WebViewCore.TouchUpData touchUpData = new WebViewCore.TouchUpData();
5172        touchUpData.mMoveGeneration = touchGeneration;
5173        touchUpData.mBuildGeneration = buildGeneration;
5174        touchUpData.mSize = size;
5175        touchUpData.mRetry = retry;
5176        mFocusData.mFrame = touchUpData.mFrame = frame;
5177        mFocusData.mNode = touchUpData.mNode = node;
5178        mFocusData.mX = touchUpData.mX = x;
5179        mFocusData.mY = touchUpData.mY = y;
5180        mWebViewCore.sendMessage(EventHub.TOUCH_UP, touchUpData);
5181    }
5182
5183
5184    private int getScaledMaxXScroll() {
5185        int width;
5186        if (mHeightCanMeasure == false) {
5187            width = getViewWidth() / 4;
5188        } else {
5189            Rect visRect = new Rect();
5190            calcOurVisibleRect(visRect);
5191            width = visRect.width() / 2;
5192        }
5193        // FIXME the divisor should be retrieved from somewhere
5194        return viewToContent(width);
5195    }
5196
5197    private int getScaledMaxYScroll() {
5198        int height;
5199        if (mHeightCanMeasure == false) {
5200            height = getViewHeight() / 4;
5201        } else {
5202            Rect visRect = new Rect();
5203            calcOurVisibleRect(visRect);
5204            height = visRect.height() / 2;
5205        }
5206        // FIXME the divisor should be retrieved from somewhere
5207        // the closest thing today is hard-coded into ScrollView.java
5208        // (from ScrollView.java, line 363)   int maxJump = height/2;
5209        return viewToContent(height);
5210    }
5211
5212    /**
5213     * Called by JNI to invalidate view
5214     */
5215    private void viewInvalidate() {
5216        invalidate();
5217    }
5218
5219    // return true if the key was handled
5220    private boolean navHandledKey(int keyCode, int count, boolean noScroll
5221            , long time) {
5222        if (mNativeClass == 0) {
5223            return false;
5224        }
5225        mLastFocusTime = time;
5226        mLastFocusBounds = nativeGetFocusRingBounds();
5227        boolean keyHandled = nativeMoveFocus(keyCode, count, noScroll) == false;
5228        if (LOGV_ENABLED) {
5229            Log.v(LOGTAG, "navHandledKey mLastFocusBounds=" + mLastFocusBounds
5230                    + " mLastFocusTime=" + mLastFocusTime
5231                    + " handled=" + keyHandled);
5232        }
5233        if (keyHandled == false || mHeightCanMeasure == false) {
5234            return keyHandled;
5235        }
5236        Rect contentFocus = nativeGetFocusRingBounds();
5237        if (contentFocus.isEmpty()) return keyHandled;
5238        Rect viewFocus = contentToView(contentFocus);
5239        Rect visRect = new Rect();
5240        calcOurVisibleRect(visRect);
5241        Rect outset = new Rect(visRect);
5242        int maxXScroll = visRect.width() / 2;
5243        int maxYScroll = visRect.height() / 2;
5244        outset.inset(-maxXScroll, -maxYScroll);
5245        if (Rect.intersects(outset, viewFocus) == false) {
5246            return keyHandled;
5247        }
5248        // FIXME: Necessary because ScrollView/ListView do not scroll left/right
5249        int maxH = Math.min(viewFocus.right - visRect.right, maxXScroll);
5250        if (maxH > 0) {
5251            pinScrollBy(maxH, 0, true, 0);
5252        } else {
5253            maxH = Math.max(viewFocus.left - visRect.left, -maxXScroll);
5254            if (maxH < 0) {
5255                pinScrollBy(maxH, 0, true, 0);
5256            }
5257        }
5258        if (mLastFocusBounds.isEmpty()) return keyHandled;
5259        if (mLastFocusBounds.equals(contentFocus)) return keyHandled;
5260        if (LOGV_ENABLED) {
5261            Log.v(LOGTAG, "navHandledKey contentFocus=" + contentFocus);
5262        }
5263        requestRectangleOnScreen(viewFocus);
5264        mUserScroll = true;
5265        return keyHandled;
5266    }
5267
5268    /**
5269     * Set the background color. It's white by default. Pass
5270     * zero to make the view transparent.
5271     * @param color   the ARGB color described by Color.java
5272     */
5273    public void setBackgroundColor(int color) {
5274        mBackgroundColor = color;
5275        mWebViewCore.sendMessage(EventHub.SET_BACKGROUND_COLOR, color);
5276    }
5277
5278    public void debugDump() {
5279        nativeDebugDump();
5280        mWebViewCore.sendMessage(EventHub.DUMP_NAVTREE);
5281    }
5282
5283    /**
5284     *  Update our cache with updatedText.
5285     *  @param updatedText  The new text to put in our cache.
5286     */
5287    /* package */ void updateCachedTextfield(String updatedText) {
5288        // Also place our generation number so that when we look at the cache
5289        // we recognize that it is up to date.
5290        nativeUpdateCachedTextfield(updatedText, mTextGeneration);
5291    }
5292
5293    // Never call this version except by updateCachedTextfield(String) -
5294    // we always want to pass in our generation number.
5295    private native void     nativeUpdateCachedTextfield(String updatedText,
5296            int generation);
5297    private native void     nativeClearFocus(int x, int y);
5298    private native void     nativeCreate(int ptr);
5299    private native void     nativeDebugDump();
5300    private native void     nativeDestroy();
5301    private native void     nativeDrawFocusRing(Canvas content);
5302    private native void     nativeDrawSelection(Canvas content
5303            , int x, int y, boolean extendSelection);
5304    private native void     nativeDrawSelectionRegion(Canvas content);
5305    private native boolean  nativeUpdateFocusNode();
5306    private native Rect     nativeGetFocusRingBounds();
5307    private native Rect     nativeGetNavBounds();
5308    private native void     nativeInstrumentReport();
5309    private native void     nativeMarkNodeInvalid(int node);
5310    // return true if the page has been scrolled
5311    private native boolean  nativeMotionUp(int x, int y, int slop);
5312    // returns false if it handled the key
5313    private native boolean  nativeMoveFocus(int keyCode, int count,
5314            boolean noScroll);
5315    private native void     nativeNotifyFocusSet(boolean inEditingMode);
5316    private native void     nativeRecomputeFocus();
5317    // Like many other of our native methods, you must make sure that
5318    // mNativeClass is not null before calling this method.
5319    private native void     nativeRecordButtons(boolean focused,
5320            boolean pressed, boolean invalidate);
5321    private native void     nativeResetFocus();
5322    private native void     nativeResetNavClipBounds();
5323    private native void     nativeSelectBestAt(Rect rect);
5324    private native void     nativeSetFindIsDown();
5325    private native void     nativeSetFollowedLink(boolean followed);
5326    private native void     nativeSetHeightCanMeasure(boolean measure);
5327    private native void     nativeSetNavBounds(Rect rect);
5328    private native void     nativeSetNavClipBounds(Rect rect);
5329    private native String   nativeImageURI(int x, int y);
5330    /**
5331     * Returns true if the native focus nodes says it wants to handle key events
5332     * (ala plugins). This can only be called if mNativeClass is non-zero!
5333     */
5334    private native boolean  nativeFocusNodeWantsKeyEvents();
5335    private native void     nativeMoveSelection(int x, int y
5336            , boolean extendSelection);
5337    private native Region   nativeGetSelection();
5338
5339    private native void nativeDumpDisplayTree(String urlOrNull);
5340}
5341