WebView.java revision 843bbb8597d561c2541f86d1f391ae65261cf743
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.graphics.Bitmap;
25import android.graphics.Canvas;
26import android.graphics.Color;
27import android.graphics.Paint;
28import android.graphics.Path;
29import android.graphics.Picture;
30import android.graphics.Point;
31import android.graphics.Rect;
32import android.graphics.Region;
33import android.net.http.SslCertificate;
34import android.net.Uri;
35import android.os.Bundle;
36import android.os.Handler;
37import android.os.Message;
38import android.os.ServiceManager;
39import android.os.SystemClock;
40import android.provider.Checkin;
41import android.text.IClipboard;
42import android.text.Selection;
43import android.text.Spannable;
44import android.util.AttributeSet;
45import android.util.EventLog;
46import android.util.Log;
47import android.view.Gravity;
48import android.view.KeyEvent;
49import android.view.LayoutInflater;
50import android.view.MotionEvent;
51import android.view.SoundEffectConstants;
52import android.view.VelocityTracker;
53import android.view.View;
54import android.view.ViewConfiguration;
55import android.view.ViewGroup;
56import android.view.ViewParent;
57import android.view.ViewTreeObserver;
58import android.view.animation.AlphaAnimation;
59import android.view.inputmethod.InputMethodManager;
60import android.webkit.TextDialog.AutoCompleteAdapter;
61import android.webkit.WebViewCore.EventHub;
62import android.widget.AbsoluteLayout;
63import android.widget.AdapterView;
64import android.widget.ArrayAdapter;
65import android.widget.FrameLayout;
66import android.widget.ImageView;
67import android.widget.ListView;
68import android.widget.Scroller;
69import android.widget.Toast;
70import android.widget.ZoomButtonsController;
71import android.widget.ZoomControls;
72import android.widget.AdapterView.OnItemClickListener;
73
74import java.io.File;
75import java.io.FileInputStream;
76import java.io.FileNotFoundException;
77import java.io.FileOutputStream;
78import java.io.IOException;
79import java.net.URLDecoder;
80import java.util.ArrayList;
81import java.util.HashMap;
82import java.util.List;
83
84/**
85 * <p>A View that displays web pages. This class is the basis upon which you
86 * can roll your own web browser or simply display some online content within your Activity.
87 * It uses the WebKit rendering engine to display
88 * web pages and includes methods to navigate forward and backward
89 * through a history, zoom in and out, perform text searches and more.</p>
90 * <p>To enable the built-in zoom, set
91 * {@link #getSettings() WebSettings}.{@link WebSettings#setBuiltInZoomControls(boolean)}
92 * (introduced in API version 3).
93 * <p>Note that, in order for your Activity to access the Internet and load web pages
94 * in a WebView, you must add the <var>INTERNET</var> permissions to your
95 * Android Manifest file:</p>
96 * <pre>&lt;uses-permission android:name="android.permission.INTERNET" /></pre>
97 *
98 * <p>This must be a child of the <code>&lt;manifest></code> element.</p>
99 *
100 * <h3>Basic usage</h3>
101 *
102 * <p>By default, a WebView provides no browser-like widgets, does not
103 * enable JavaScript and errors will be ignored. If your goal is only
104 * to display some HTML as a part of your UI, this is probably fine;
105 * the user won't need to interact with the web page beyond reading
106 * it, and the web page won't need to interact with the user. If you
107 * actually want a fully blown web browser, then you probably want to
108 * invoke the Browser application with your URL rather than show it
109 * with a WebView. See {@link android.content.Intent} for more information.</p>
110 *
111 * <pre class="prettyprint">
112 * WebView webview = new WebView(this);
113 * setContentView(webview);
114 *
115 * // Simplest usage: note that an exception will NOT be thrown
116 * // if there is an error loading this page (see below).
117 * webview.loadUrl("http://slashdot.org/");
118 *
119 * // Of course you can also load from any string:
120 * String summary = "&lt;html>&lt;body>You scored &lt;b>192</b> points.&lt;/body>&lt;/html>";
121 * webview.loadData(summary, "text/html", "utf-8");
122 * // ... although note that there are restrictions on what this HTML can do.
123 * // See the JavaDocs for loadData and loadDataWithBaseUrl for more info.
124 * </pre>
125 *
126 * <p>A WebView has several customization points where you can add your
127 * own behavior. These are:</p>
128 *
129 * <ul>
130 *   <li>Creating and setting a {@link android.webkit.WebChromeClient} subclass.
131 *       This class is called when something that might impact a
132 *       browser UI happens, for instance, progress updates and
133 *       JavaScript alerts are sent here.
134 *   </li>
135 *   <li>Creating and setting a {@link android.webkit.WebViewClient} subclass.
136 *       It will be called when things happen that impact the
137 *       rendering of the content, eg, errors or form submissions. You
138 *       can also intercept URL loading here.</li>
139 *   <li>Via the {@link android.webkit.WebSettings} class, which contains
140 *       miscellaneous configuration. </li>
141 *   <li>With the {@link android.webkit.WebView#addJavascriptInterface} method.
142 *       This lets you bind Java objects into the WebView so they can be
143 *       controlled from the web pages JavaScript.</li>
144 * </ul>
145 *
146 * <p>Here's a more complicated example, showing error handling,
147 *    settings, and progress notification:</p>
148 *
149 * <pre class="prettyprint">
150 * // Let's display the progress in the activity title bar, like the
151 * // browser app does.
152 * getWindow().requestFeature(Window.FEATURE_PROGRESS);
153 *
154 * webview.getSettings().setJavaScriptEnabled(true);
155 *
156 * final Activity activity = this;
157 * webview.setWebChromeClient(new WebChromeClient() {
158 *   public void onProgressChanged(WebView view, int progress) {
159 *     // Activities and WebViews measure progress with different scales.
160 *     // The progress meter will automatically disappear when we reach 100%
161 *     activity.setProgress(progress * 1000);
162 *   }
163 * });
164 * webview.setWebViewClient(new WebViewClient() {
165 *   public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
166 *     Toast.makeText(activity, "Oh no! " + description, Toast.LENGTH_SHORT).show();
167 *   }
168 * });
169 *
170 * webview.loadUrl("http://slashdot.org/");
171 * </pre>
172 *
173 * <h3>Cookie and window management</h3>
174 *
175 * <p>For obvious security reasons, your application has its own
176 * cache, cookie store etc - it does not share the Browser
177 * applications data. Cookies are managed on a separate thread, so
178 * operations like index building don't block the UI
179 * thread. Follow the instructions in {@link android.webkit.CookieSyncManager}
180 * if you want to use cookies in your application.
181 * </p>
182 *
183 * <p>By default, requests by the HTML to open new windows are
184 * ignored. This is true whether they be opened by JavaScript or by
185 * the target attribute on a link. You can customize your
186 * WebChromeClient to provide your own behaviour for opening multiple windows,
187 * and render them in whatever manner you want.</p>
188 *
189 * <p>Standard behavior for an Activity is to be destroyed and
190 * recreated when the devices orientation is changed. This will cause
191 * the WebView to reload the current page. If you don't want that, you
192 * can set your Activity to handle the orientation and keyboardHidden
193 * changes, and then just leave the WebView alone. It'll automatically
194 * re-orient itself as appropriate.</p>
195 */
196public class WebView extends AbsoluteLayout
197        implements ViewTreeObserver.OnGlobalFocusChangeListener,
198        ViewGroup.OnHierarchyChangeListener {
199
200    // if AUTO_REDRAW_HACK is true, then the CALL key will toggle redrawing
201    // the screen all-the-time. Good for profiling our drawing code
202    static private final boolean AUTO_REDRAW_HACK = false;
203    // true means redraw the screen all-the-time. Only with AUTO_REDRAW_HACK
204    private boolean mAutoRedraw;
205
206    // keep debugging parameters near the top of the file
207    static final String LOGTAG = "webview";
208    static final boolean DEBUG = false;
209    static final boolean LOGV_ENABLED = DEBUG;
210
211    private class ExtendedZoomControls extends FrameLayout {
212        public ExtendedZoomControls(Context context, AttributeSet attrs) {
213            super(context, attrs);
214            LayoutInflater inflater = (LayoutInflater)
215                    context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
216            inflater.inflate(com.android.internal.R.layout.zoom_magnify, this, true);
217            mZoomControls = (ZoomControls) findViewById(com.android.internal.R.id.zoomControls);
218            mZoomMagnify = (ImageView) findViewById(com.android.internal.R.id.zoomMagnify);
219        }
220
221        public void show(boolean showZoom, boolean canZoomOut) {
222            mZoomControls.setVisibility(showZoom ? View.VISIBLE : View.GONE);
223            mZoomMagnify.setVisibility(canZoomOut ? View.VISIBLE : View.GONE);
224            fade(View.VISIBLE, 0.0f, 1.0f);
225        }
226
227        public void hide() {
228            fade(View.GONE, 1.0f, 0.0f);
229        }
230
231        private void fade(int visibility, float startAlpha, float endAlpha) {
232            AlphaAnimation anim = new AlphaAnimation(startAlpha, endAlpha);
233            anim.setDuration(500);
234            startAnimation(anim);
235            setVisibility(visibility);
236        }
237
238        public void setIsZoomMagnifyEnabled(boolean isEnabled) {
239            mZoomMagnify.setEnabled(isEnabled);
240        }
241
242        public boolean hasFocus() {
243            return mZoomControls.hasFocus() || mZoomMagnify.hasFocus();
244        }
245
246        public void setOnZoomInClickListener(OnClickListener listener) {
247            mZoomControls.setOnZoomInClickListener(listener);
248        }
249
250        public void setOnZoomOutClickListener(OnClickListener listener) {
251            mZoomControls.setOnZoomOutClickListener(listener);
252        }
253
254        public void setOnZoomMagnifyClickListener(OnClickListener listener) {
255            mZoomMagnify.setOnClickListener(listener);
256        }
257
258        ZoomControls mZoomControls;
259        ImageView mZoomMagnify;
260    }
261
262    /**
263     *  Transportation object for returning WebView across thread boundaries.
264     */
265    public class WebViewTransport {
266        private WebView mWebview;
267
268        /**
269         * Set the WebView to the transportation object.
270         * @param webview The WebView to transport.
271         */
272        public synchronized void setWebView(WebView webview) {
273            mWebview = webview;
274        }
275
276        /**
277         * Return the WebView object.
278         * @return WebView The transported WebView object.
279         */
280        public synchronized WebView getWebView() {
281            return mWebview;
282        }
283    }
284
285    // A final CallbackProxy shared by WebViewCore and BrowserFrame.
286    private final CallbackProxy mCallbackProxy;
287
288    private final WebViewDatabase mDatabase;
289
290    // SSL certificate for the main top-level page (if secure)
291    private SslCertificate mCertificate;
292
293    // Native WebView pointer that is 0 until the native object has been
294    // created.
295    private int mNativeClass;
296    // This would be final but it needs to be set to null when the WebView is
297    // destroyed.
298    private WebViewCore mWebViewCore;
299    // Handler for dispatching UI messages.
300    /* package */ final Handler mPrivateHandler = new PrivateHandler();
301    private TextDialog mTextEntry;
302    // Used to ignore changes to webkit text that arrives to the UI side after
303    // more key events.
304    private int mTextGeneration;
305
306    // The list of loaded plugins.
307    private static PluginList sPluginList;
308
309    /**
310     * Position of the last touch event.
311     */
312    private float mLastTouchX;
313    private float mLastTouchY;
314
315    /**
316     * Time of the last touch event.
317     */
318    private long mLastTouchTime;
319
320    /**
321     * Time of the last time sending touch event to WebViewCore
322     */
323    private long mLastSentTouchTime;
324
325    /**
326     * The minimum elapsed time before sending another ACTION_MOVE event to
327     * WebViewCore
328     */
329    private static final int TOUCH_SENT_INTERVAL = 100;
330
331    /**
332     * Helper class to get velocity for fling
333     */
334    VelocityTracker mVelocityTracker;
335
336    /**
337     * Touch mode
338     */
339    private int mTouchMode = TOUCH_DONE_MODE;
340    private static final int TOUCH_INIT_MODE = 1;
341    private static final int TOUCH_DRAG_START_MODE = 2;
342    private static final int TOUCH_DRAG_MODE = 3;
343    private static final int TOUCH_SHORTPRESS_START_MODE = 4;
344    private static final int TOUCH_SHORTPRESS_MODE = 5;
345    private static final int TOUCH_DOUBLECLICK_MODE = 6;
346    private static final int TOUCH_DONE_MODE = 7;
347    private static final int TOUCH_SELECT_MODE = 8;
348    // touch mode values specific to scale+scroll
349    private static final int FIRST_SCROLL_ZOOM = 9;
350    private static final int SCROLL_ZOOM_ANIMATION_IN = 9;
351    private static final int SCROLL_ZOOM_ANIMATION_OUT = 10;
352    private static final int SCROLL_ZOOM_OUT = 11;
353    private static final int LAST_SCROLL_ZOOM = 11;
354    // end of touch mode values specific to scale+scroll
355
356    // Whether to forward the touch events to WebCore
357    private boolean mForwardTouchEvents = false;
358
359    // Whether to prevent drag during touch. The initial value depends on
360    // mForwardTouchEvents. If WebCore wants touch events, we assume it will
361    // take control of touch events unless it says no for touch down event.
362    private boolean mPreventDrag;
363
364    // If updateTextEntry gets called while we are out of focus, use this
365    // variable to remember to do it next time we gain focus.
366    private boolean mNeedsUpdateTextEntry = false;
367
368    // Whether or not to draw the focus ring.
369    private boolean mDrawFocusRing = true;
370
371    /**
372     * Customizable constant
373     */
374    // pre-computed square of ViewConfiguration.getScaledTouchSlop()
375    private int mTouchSlopSquare;
376    // pre-computed density adjusted navigation slop
377    private int mNavSlop;
378    // This should be ViewConfiguration.getTapTimeout()
379    // But system time out is 100ms, which is too short for the browser.
380    // In the browser, if it switches out of tap too soon, jump tap won't work.
381    private static final int TAP_TIMEOUT = 200;
382    // This should be ViewConfiguration.getLongPressTimeout()
383    // But system time out is 500ms, which is too short for the browser.
384    // With a short timeout, it's difficult to treat trigger a short press.
385    private static final int LONG_PRESS_TIMEOUT = 1000;
386    // needed to avoid flinging after a pause of no movement
387    private static final int MIN_FLING_TIME = 250;
388    // The time that the Zoom Controls are visible before fading away
389    private static final long ZOOM_CONTROLS_TIMEOUT =
390            ViewConfiguration.getZoomControlsTimeout();
391    // The amount of content to overlap between two screens when going through
392    // pages with the space bar, in pixels.
393    private static final int PAGE_SCROLL_OVERLAP = 24;
394
395    /**
396     * These prevent calling requestLayout if either dimension is fixed. This
397     * depends on the layout parameters and the measure specs.
398     */
399    boolean mWidthCanMeasure;
400    boolean mHeightCanMeasure;
401
402    // Remember the last dimensions we sent to the native side so we can avoid
403    // sending the same dimensions more than once.
404    int mLastWidthSent;
405    int mLastHeightSent;
406
407    private int mContentWidth;   // cache of value from WebViewCore
408    private int mContentHeight;  // cache of value from WebViewCore
409
410    // Need to have the separate control for horizontal and vertical scrollbar
411    // style than the View's single scrollbar style
412    private boolean mOverlayHorizontalScrollbar = true;
413    private boolean mOverlayVerticalScrollbar = false;
414
415    // our standard speed. this way small distances will be traversed in less
416    // time than large distances, but we cap the duration, so that very large
417    // distances won't take too long to get there.
418    private static final int STD_SPEED = 480;  // pixels per second
419    // time for the longest scroll animation
420    private static final int MAX_DURATION = 750;   // milliseconds
421    private Scroller mScroller;
422
423    private boolean mWrapContent;
424
425    // true if we should call webcore to draw the content, false means we have
426    // requested something but it isn't ready to draw yet.
427    private WebViewCore.FocusData mFocusData;
428    /**
429     * Private message ids
430     */
431    private static final int REMEMBER_PASSWORD = 1;
432    private static final int NEVER_REMEMBER_PASSWORD = 2;
433    private static final int SWITCH_TO_SHORTPRESS = 3;
434    private static final int SWITCH_TO_LONGPRESS = 4;
435    private static final int UPDATE_TEXT_ENTRY_ADAPTER = 6;
436    private static final int SWITCH_TO_ENTER = 7;
437    private static final int RESUME_WEBCORE_UPDATE = 8;
438
439    //! arg1=x, arg2=y
440    static final int SCROLL_TO_MSG_ID               = 10;
441    static final int SCROLL_BY_MSG_ID               = 11;
442    //! arg1=x, arg2=y
443    static final int SPAWN_SCROLL_TO_MSG_ID         = 12;
444    //! arg1=x, arg2=y
445    static final int SYNC_SCROLL_TO_MSG_ID          = 13;
446    static final int NEW_PICTURE_MSG_ID             = 14;
447    static final int UPDATE_TEXT_ENTRY_MSG_ID       = 15;
448    static final int WEBCORE_INITIALIZED_MSG_ID     = 16;
449    static final int UPDATE_TEXTFIELD_TEXT_MSG_ID   = 17;
450    static final int DID_FIRST_LAYOUT_MSG_ID        = 18;
451    static final int RECOMPUTE_FOCUS_MSG_ID         = 19;
452    static final int NOTIFY_FOCUS_SET_MSG_ID        = 20;
453    static final int MARK_NODE_INVALID_ID           = 21;
454    static final int UPDATE_CLIPBOARD               = 22;
455    static final int LONG_PRESS_ENTER               = 23;
456    static final int PREVENT_TOUCH_ID               = 24;
457    static final int WEBCORE_NEED_TOUCH_EVENTS      = 25;
458    // obj=Rect in doc coordinates
459    static final int INVAL_RECT_MSG_ID              = 26;
460
461    static final String[] HandlerDebugString = {
462        "REMEMBER_PASSWORD", // = 1;
463        "NEVER_REMEMBER_PASSWORD", // = 2;
464        "SWITCH_TO_SHORTPRESS", // = 3;
465        "SWITCH_TO_LONGPRESS", // = 4;
466        "5",
467        "UPDATE_TEXT_ENTRY_ADAPTER", // = 6;
468        "SWITCH_TO_ENTER", // = 7;
469        "RESUME_WEBCORE_UPDATE", // = 8;
470        "9",
471        "SCROLL_TO_MSG_ID", //               = 10;
472        "SCROLL_BY_MSG_ID", //               = 11;
473        "SPAWN_SCROLL_TO_MSG_ID", //         = 12;
474        "SYNC_SCROLL_TO_MSG_ID", //          = 13;
475        "NEW_PICTURE_MSG_ID", //             = 14;
476        "UPDATE_TEXT_ENTRY_MSG_ID", //       = 15;
477        "WEBCORE_INITIALIZED_MSG_ID", //     = 16;
478        "UPDATE_TEXTFIELD_TEXT_MSG_ID", //   = 17;
479        "DID_FIRST_LAYOUT_MSG_ID", //        = 18;
480        "RECOMPUTE_FOCUS_MSG_ID", //         = 19;
481        "NOTIFY_FOCUS_SET_MSG_ID", //        = 20;
482        "MARK_NODE_INVALID_ID", //           = 21;
483        "UPDATE_CLIPBOARD", //               = 22;
484        "LONG_PRESS_ENTER", //               = 23;
485        "PREVENT_TOUCH_ID", //               = 24;
486        "WEBCORE_NEED_TOUCH_EVENTS", //      = 25;
487        "INVAL_RECT_MSG_ID" //               = 26;
488    };
489
490    // width which view is considered to be fully zoomed out
491    static final int ZOOM_OUT_WIDTH = 1008;
492
493    private static final float DEFAULT_MAX_ZOOM_SCALE = 4.0f;
494    private static final float DEFAULT_MIN_ZOOM_SCALE = 0.25f;
495    // scale limit, which can be set through viewport meta tag in the web page
496    private float mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE;
497    private float mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE;
498    private boolean mMinZoomScaleFixed = false;
499
500    // initial scale in percent. 0 means using default.
501    private int mInitialScale = 0;
502
503    // set to true temporarily while the zoom control is being dragged
504    private boolean mPreviewZoomOnly = false;
505
506    // computed scale and inverse, from mZoomWidth.
507    private float mActualScale = 1;
508    private float mInvActualScale = 1;
509    // if this is non-zero, it is used on drawing rather than mActualScale
510    private float mZoomScale;
511    private float mInvInitialZoomScale;
512    private float mInvFinalZoomScale;
513    private long mZoomStart;
514    private static final int ZOOM_ANIMATION_LENGTH = 500;
515
516    private boolean mUserScroll = false;
517
518    private int mSnapScrollMode = SNAP_NONE;
519    private static final int SNAP_NONE = 1;
520    private static final int SNAP_X = 2;
521    private static final int SNAP_Y = 3;
522    private static final int SNAP_X_LOCK = 4;
523    private static final int SNAP_Y_LOCK = 5;
524    private boolean mSnapPositive;
525
526    // Used to match key downs and key ups
527    private boolean mGotKeyDown;
528
529    /* package */ static boolean mLogEvent = true;
530    private static final int EVENT_LOG_ZOOM_LEVEL_CHANGE = 70101;
531    private static final int EVENT_LOG_DOUBLE_TAP_DURATION = 70102;
532
533    // for event log
534    private long mLastTouchUpTime = 0;
535
536    /**
537     * URI scheme for telephone number
538     */
539    public static final String SCHEME_TEL = "tel:";
540    /**
541     * URI scheme for email address
542     */
543    public static final String SCHEME_MAILTO = "mailto:";
544    /**
545     * URI scheme for map address
546     */
547    public static final String SCHEME_GEO = "geo:0,0?q=";
548
549    private int mBackgroundColor = Color.WHITE;
550
551    // Used to notify listeners of a new picture.
552    private PictureListener mPictureListener;
553    /**
554     * Interface to listen for new pictures as they change.
555     */
556    public interface PictureListener {
557        /**
558         * Notify the listener that the picture has changed.
559         * @param view The WebView that owns the picture.
560         * @param picture The new picture.
561         */
562        public void onNewPicture(WebView view, Picture picture);
563    }
564
565    public class HitTestResult {
566        /**
567         * Default HitTestResult, where the target is unknown
568         */
569        public static final int UNKNOWN_TYPE = 0;
570        /**
571         * HitTestResult for hitting a HTML::a tag
572         */
573        public static final int ANCHOR_TYPE = 1;
574        /**
575         * HitTestResult for hitting a phone number
576         */
577        public static final int PHONE_TYPE = 2;
578        /**
579         * HitTestResult for hitting a map address
580         */
581        public static final int GEO_TYPE = 3;
582        /**
583         * HitTestResult for hitting an email address
584         */
585        public static final int EMAIL_TYPE = 4;
586        /**
587         * HitTestResult for hitting an HTML::img tag
588         */
589        public static final int IMAGE_TYPE = 5;
590        /**
591         * HitTestResult for hitting a HTML::a tag which contains HTML::img
592         */
593        public static final int IMAGE_ANCHOR_TYPE = 6;
594        /**
595         * HitTestResult for hitting a HTML::a tag with src=http
596         */
597        public static final int SRC_ANCHOR_TYPE = 7;
598        /**
599         * HitTestResult for hitting a HTML::a tag with src=http + HTML::img
600         */
601        public static final int SRC_IMAGE_ANCHOR_TYPE = 8;
602        /**
603         * HitTestResult for hitting an edit text area
604         */
605        public static final int EDIT_TEXT_TYPE = 9;
606
607        private int mType;
608        private String mExtra;
609
610        HitTestResult() {
611            mType = UNKNOWN_TYPE;
612        }
613
614        private void setType(int type) {
615            mType = type;
616        }
617
618        private void setExtra(String extra) {
619            mExtra = extra;
620        }
621
622        public int getType() {
623            return mType;
624        }
625
626        public String getExtra() {
627            return mExtra;
628        }
629    }
630
631    // The View containing the zoom controls
632    private ExtendedZoomControls mZoomControls;
633    private Runnable mZoomControlRunnable;
634
635    private ZoomButtonsController mZoomButtonsController;
636    private ImageView mZoomOverviewButton;
637    private ImageView mZoomFitPageButton;
638
639    // These keep track of the center point of the zoom.  They are used to
640    // determine the point around which we should zoom.
641    private float mZoomCenterX;
642    private float mZoomCenterY;
643
644    private ZoomButtonsController.OnZoomListener mZoomListener =
645            new ZoomButtonsController.OnZoomListener() {
646
647        public void onVisibilityChanged(boolean visible) {
648            if (visible) {
649                switchOutDrawHistory();
650                updateZoomButtonsEnabled();
651            }
652        }
653
654        public void onZoom(boolean zoomIn) {
655            if (zoomIn) {
656                zoomIn();
657            } else {
658                zoomOut();
659            }
660
661            updateZoomButtonsEnabled();
662        }
663    };
664
665    /**
666     * Construct a new WebView with a Context object.
667     * @param context A Context object used to access application assets.
668     */
669    public WebView(Context context) {
670        this(context, null);
671    }
672
673    /**
674     * Construct a new WebView with layout parameters.
675     * @param context A Context object used to access application assets.
676     * @param attrs An AttributeSet passed to our parent.
677     */
678    public WebView(Context context, AttributeSet attrs) {
679        this(context, attrs, com.android.internal.R.attr.webViewStyle);
680    }
681
682    /**
683     * Construct a new WebView with layout parameters and a default style.
684     * @param context A Context object used to access application assets.
685     * @param attrs An AttributeSet passed to our parent.
686     * @param defStyle The default style resource ID.
687     */
688    public WebView(Context context, AttributeSet attrs, int defStyle) {
689        super(context, attrs, defStyle);
690        init();
691
692        mCallbackProxy = new CallbackProxy(context, this);
693        mWebViewCore = new WebViewCore(context, this, mCallbackProxy);
694        mDatabase = WebViewDatabase.getInstance(context);
695        mFocusData = new WebViewCore.FocusData();
696        mFocusData.mFrame = 0;
697        mFocusData.mNode = 0;
698        mFocusData.mX = 0;
699        mFocusData.mY = 0;
700        mScroller = new Scroller(context);
701
702        initZoomController(context);
703    }
704
705    private void initZoomController(Context context) {
706        // Create the buttons controller
707        mZoomButtonsController = new ZoomButtonsController(this);
708        mZoomButtonsController.setOnZoomListener(mZoomListener);
709
710        // Create the accessory buttons
711        LayoutInflater inflater =
712                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
713        ViewGroup container = mZoomButtonsController.getContainer();
714        inflater.inflate(com.android.internal.R.layout.zoom_browser_accessory_buttons, container);
715        mZoomOverviewButton =
716                (ImageView) container.findViewById(com.android.internal.R.id.zoom_page_overview);
717        mZoomOverviewButton.setOnClickListener(
718            new View.OnClickListener() {
719                public void onClick(View v) {
720                    mZoomButtonsController.setVisible(false);
721                    zoomScrollOut();
722                    if (mLogEvent) {
723                        Checkin.updateStats(mContext.getContentResolver(),
724                                Checkin.Stats.Tag.BROWSER_ZOOM_OVERVIEW, 1, 0.0);
725                    }
726                }
727            });
728        mZoomFitPageButton =
729                (ImageView) container.findViewById(com.android.internal.R.id.zoom_fit_page);
730        mZoomFitPageButton.setOnClickListener(
731            new View.OnClickListener() {
732                public void onClick(View v) {
733                    zoomWithPreview(1f);
734                    updateZoomButtonsEnabled();
735                }
736            });
737    }
738
739    private void updateZoomButtonsEnabled() {
740        boolean canZoomIn = mActualScale < mMaxZoomScale;
741        boolean canZoomOut = mActualScale > mMinZoomScale;
742        if (!canZoomIn && !canZoomOut) {
743            // Hide the zoom in and out buttons, as well as the fit to page
744            // button, if the page cannot zoom
745            mZoomButtonsController.getZoomControls().setVisibility(View.GONE);
746            mZoomFitPageButton.setVisibility(View.GONE);
747        } else {
748            // Bring back the hidden zoom controls.
749            mZoomButtonsController.getZoomControls()
750                    .setVisibility(View.VISIBLE);
751            mZoomFitPageButton.setVisibility(View.VISIBLE);
752            // Set each one individually, as a page may be able to zoom in
753            // or out.
754            mZoomButtonsController.setZoomInEnabled(canZoomIn);
755            mZoomButtonsController.setZoomOutEnabled(canZoomOut);
756            mZoomFitPageButton.setEnabled(mActualScale != 1);
757        }
758        mZoomOverviewButton.setVisibility(canZoomScrollOut() ? View.VISIBLE:
759                View.GONE);
760    }
761
762    private void init() {
763        setWillNotDraw(false);
764        setFocusable(true);
765        setFocusableInTouchMode(true);
766        setClickable(true);
767        setLongClickable(true);
768
769        final int slop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
770        mTouchSlopSquare = slop * slop;
771        mMinLockSnapReverseDistance = slop;
772        // use one line height, 16 based on our current default font, for how
773        // far we allow a touch be away from the edge of a link
774        mNavSlop = (int) (16 * getContext().getResources()
775                .getDisplayMetrics().density);
776    }
777
778    /* package */ boolean onSavePassword(String schemePlusHost, String username,
779            String password, final Message resumeMsg) {
780       boolean rVal = false;
781       if (resumeMsg == null) {
782           // null resumeMsg implies saving password silently
783           mDatabase.setUsernamePassword(schemePlusHost, username, password);
784       } else {
785            final Message remember = mPrivateHandler.obtainMessage(
786                    REMEMBER_PASSWORD);
787            remember.getData().putString("host", schemePlusHost);
788            remember.getData().putString("username", username);
789            remember.getData().putString("password", password);
790            remember.obj = resumeMsg;
791
792            final Message neverRemember = mPrivateHandler.obtainMessage(
793                    NEVER_REMEMBER_PASSWORD);
794            neverRemember.getData().putString("host", schemePlusHost);
795            neverRemember.getData().putString("username", username);
796            neverRemember.getData().putString("password", password);
797            neverRemember.obj = resumeMsg;
798
799            new AlertDialog.Builder(getContext())
800                    .setTitle(com.android.internal.R.string.save_password_label)
801                    .setMessage(com.android.internal.R.string.save_password_message)
802                    .setPositiveButton(com.android.internal.R.string.save_password_notnow,
803                    new DialogInterface.OnClickListener() {
804                        public void onClick(DialogInterface dialog, int which) {
805                            resumeMsg.sendToTarget();
806                        }
807                    })
808                    .setNeutralButton(com.android.internal.R.string.save_password_remember,
809                    new DialogInterface.OnClickListener() {
810                        public void onClick(DialogInterface dialog, int which) {
811                            remember.sendToTarget();
812                        }
813                    })
814                    .setNegativeButton(com.android.internal.R.string.save_password_never,
815                    new DialogInterface.OnClickListener() {
816                        public void onClick(DialogInterface dialog, int which) {
817                            neverRemember.sendToTarget();
818                        }
819                    })
820                    .setOnCancelListener(new OnCancelListener() {
821                        public void onCancel(DialogInterface dialog) {
822                            resumeMsg.sendToTarget();
823                        }
824                    }).show();
825            // Return true so that WebViewCore will pause while the dialog is
826            // up.
827            rVal = true;
828        }
829       return rVal;
830    }
831
832    @Override
833    public void setScrollBarStyle(int style) {
834        if (style == View.SCROLLBARS_INSIDE_INSET
835                || style == View.SCROLLBARS_OUTSIDE_INSET) {
836            mOverlayHorizontalScrollbar = mOverlayVerticalScrollbar = false;
837        } else {
838            mOverlayHorizontalScrollbar = mOverlayVerticalScrollbar = true;
839        }
840        super.setScrollBarStyle(style);
841    }
842
843    /**
844     * Specify whether the horizontal scrollbar has overlay style.
845     * @param overlay TRUE if horizontal scrollbar should have overlay style.
846     */
847    public void setHorizontalScrollbarOverlay(boolean overlay) {
848        mOverlayHorizontalScrollbar = overlay;
849    }
850
851    /**
852     * Specify whether the vertical scrollbar has overlay style.
853     * @param overlay TRUE if vertical scrollbar should have overlay style.
854     */
855    public void setVerticalScrollbarOverlay(boolean overlay) {
856        mOverlayVerticalScrollbar = overlay;
857    }
858
859    /**
860     * Return whether horizontal scrollbar has overlay style
861     * @return TRUE if horizontal scrollbar has overlay style.
862     */
863    public boolean overlayHorizontalScrollbar() {
864        return mOverlayHorizontalScrollbar;
865    }
866
867    /**
868     * Return whether vertical scrollbar has overlay style
869     * @return TRUE if vertical scrollbar has overlay style.
870     */
871    public boolean overlayVerticalScrollbar() {
872        return mOverlayVerticalScrollbar;
873    }
874
875    /*
876     * Return the width of the view where the content of WebView should render
877     * to.
878     */
879    private int getViewWidth() {
880        if (!isVerticalScrollBarEnabled() || mOverlayVerticalScrollbar) {
881            return getWidth();
882        } else {
883            return getWidth() - getVerticalScrollbarWidth();
884        }
885    }
886
887    /*
888     * Return the height of the view where the content of WebView should render
889     * to.
890     */
891    private int getViewHeight() {
892        if (!isHorizontalScrollBarEnabled() || mOverlayHorizontalScrollbar) {
893            return getHeight();
894        } else {
895            return getHeight() - getHorizontalScrollbarHeight();
896        }
897    }
898
899    /**
900     * @return The SSL certificate for the main top-level page or null if
901     * there is no certificate (the site is not secure).
902     */
903    public SslCertificate getCertificate() {
904        return mCertificate;
905    }
906
907    /**
908     * Sets the SSL certificate for the main top-level page.
909     */
910    public void setCertificate(SslCertificate certificate) {
911        // here, the certificate can be null (if the site is not secure)
912        mCertificate = certificate;
913    }
914
915    //-------------------------------------------------------------------------
916    // Methods called by activity
917    //-------------------------------------------------------------------------
918
919    /**
920     * Save the username and password for a particular host in the WebView's
921     * internal database.
922     * @param host The host that required the credentials.
923     * @param username The username for the given host.
924     * @param password The password for the given host.
925     */
926    public void savePassword(String host, String username, String password) {
927        mDatabase.setUsernamePassword(host, username, password);
928    }
929
930    /**
931     * Set the HTTP authentication credentials for a given host and realm.
932     *
933     * @param host The host for the credentials.
934     * @param realm The realm for the credentials.
935     * @param username The username for the password. If it is null, it means
936     *                 password can't be saved.
937     * @param password The password
938     */
939    public void setHttpAuthUsernamePassword(String host, String realm,
940            String username, String password) {
941        mDatabase.setHttpAuthUsernamePassword(host, realm, username, password);
942    }
943
944    /**
945     * Retrieve the HTTP authentication username and password for a given
946     * host & realm pair
947     *
948     * @param host The host for which the credentials apply.
949     * @param realm The realm for which the credentials apply.
950     * @return String[] if found, String[0] is username, which can be null and
951     *         String[1] is password. Return null if it can't find anything.
952     */
953    public String[] getHttpAuthUsernamePassword(String host, String realm) {
954        return mDatabase.getHttpAuthUsernamePassword(host, realm);
955    }
956
957    /**
958     * Destroy the internal state of the WebView. This method should be called
959     * after the WebView has been removed from the view system. No other
960     * methods may be called on a WebView after destroy.
961     */
962    public void destroy() {
963        clearTextEntry();
964        if (mWebViewCore != null) {
965            // Set the handlers to null before destroying WebViewCore so no
966            // more messages will be posted.
967            mCallbackProxy.setWebViewClient(null);
968            mCallbackProxy.setWebChromeClient(null);
969            // Tell WebViewCore to destroy itself
970            WebViewCore webViewCore = mWebViewCore;
971            mWebViewCore = null; // prevent using partial webViewCore
972            webViewCore.destroy();
973            // Remove any pending messages that might not be serviced yet.
974            mPrivateHandler.removeCallbacksAndMessages(null);
975            mCallbackProxy.removeCallbacksAndMessages(null);
976            // Wake up the WebCore thread just in case it is waiting for a
977            // javascript dialog.
978            synchronized (mCallbackProxy) {
979                mCallbackProxy.notify();
980            }
981        }
982        if (mNativeClass != 0) {
983            nativeDestroy();
984            mNativeClass = 0;
985        }
986    }
987
988    /**
989     * Enables platform notifications of data state and proxy changes.
990     */
991    public static void enablePlatformNotifications() {
992        Network.enablePlatformNotifications();
993    }
994
995    /**
996     * If platform notifications are enabled, this should be called
997     * from onPause() or onStop().
998     */
999    public static void disablePlatformNotifications() {
1000        Network.disablePlatformNotifications();
1001    }
1002
1003    /**
1004     * Inform WebView of the network state. This is used to set
1005     * the javascript property window.navigator.isOnline and
1006     * generates the online/offline event as specified in HTML5, sec. 5.7.7
1007     * @param networkUp boolean indicating if network is available
1008     */
1009    public void setNetworkAvailable(boolean networkUp) {
1010        mWebViewCore.sendMessage(EventHub.SET_NETWORK_STATE,
1011                networkUp ? 1 : 0, 0);
1012    }
1013
1014    /**
1015     * Save the state of this WebView used in
1016     * {@link android.app.Activity#onSaveInstanceState}. Please note that this
1017     * method no longer stores the display data for this WebView. The previous
1018     * behavior could potentially leak files if {@link #restoreState} was never
1019     * called. See {@link #savePicture} and {@link #restorePicture} for saving
1020     * and restoring the display data.
1021     * @param outState The Bundle to store the WebView state.
1022     * @return The same copy of the back/forward list used to save the state. If
1023     *         saveState fails, the returned list will be null.
1024     * @see #savePicture
1025     * @see #restorePicture
1026     */
1027    public WebBackForwardList saveState(Bundle outState) {
1028        if (outState == null) {
1029            return null;
1030        }
1031        // We grab a copy of the back/forward list because a client of WebView
1032        // may have invalidated the history list by calling clearHistory.
1033        WebBackForwardList list = copyBackForwardList();
1034        final int currentIndex = list.getCurrentIndex();
1035        final int size = list.getSize();
1036        // We should fail saving the state if the list is empty or the index is
1037        // not in a valid range.
1038        if (currentIndex < 0 || currentIndex >= size || size == 0) {
1039            return null;
1040        }
1041        outState.putInt("index", currentIndex);
1042        // FIXME: This should just be a byte[][] instead of ArrayList but
1043        // Parcel.java does not have the code to handle multi-dimensional
1044        // arrays.
1045        ArrayList<byte[]> history = new ArrayList<byte[]>(size);
1046        for (int i = 0; i < size; i++) {
1047            WebHistoryItem item = list.getItemAtIndex(i);
1048            byte[] data = item.getFlattenedData();
1049            if (data == null) {
1050                // It would be very odd to not have any data for a given history
1051                // item. And we will fail to rebuild the history list without
1052                // flattened data.
1053                return null;
1054            }
1055            history.add(data);
1056        }
1057        outState.putSerializable("history", history);
1058        if (mCertificate != null) {
1059            outState.putBundle("certificate",
1060                               SslCertificate.saveState(mCertificate));
1061        }
1062        return list;
1063    }
1064
1065    /**
1066     * Save the current display data to the Bundle given. Used in conjunction
1067     * with {@link #saveState}.
1068     * @param b A Bundle to store the display data.
1069     * @param dest The file to store the serialized picture data. Will be
1070     *             overwritten with this WebView's picture data.
1071     * @return True if the picture was successfully saved.
1072     */
1073    public boolean savePicture(Bundle b, File dest) {
1074        if (dest == null || b == null) {
1075            return false;
1076        }
1077        final Picture p = capturePicture();
1078        try {
1079            final FileOutputStream out = new FileOutputStream(dest);
1080            p.writeToStream(out);
1081            out.close();
1082        } catch (FileNotFoundException e){
1083            e.printStackTrace();
1084        } catch (IOException e) {
1085            e.printStackTrace();
1086        } catch (RuntimeException e) {
1087            e.printStackTrace();
1088        }
1089        if (dest.length() > 0) {
1090            b.putInt("scrollX", mScrollX);
1091            b.putInt("scrollY", mScrollY);
1092            b.putFloat("scale", mActualScale);
1093            return true;
1094        }
1095        return false;
1096    }
1097
1098    /**
1099     * Restore the display data that was save in {@link #savePicture}. Used in
1100     * conjunction with {@link #restoreState}.
1101     * @param b A Bundle containing the saved display data.
1102     * @param src The file where the picture data was stored.
1103     * @return True if the picture was successfully restored.
1104     */
1105    public boolean restorePicture(Bundle b, File src) {
1106        if (src == null || b == null) {
1107            return false;
1108        }
1109        if (src.exists()) {
1110            Picture p = null;
1111            try {
1112                final FileInputStream in = new FileInputStream(src);
1113                p = Picture.createFromStream(in);
1114                in.close();
1115            } catch (FileNotFoundException e){
1116                e.printStackTrace();
1117            } catch (RuntimeException e) {
1118                e.printStackTrace();
1119            } catch (IOException e) {
1120                e.printStackTrace();
1121            }
1122            if (p != null) {
1123                int sx = b.getInt("scrollX", 0);
1124                int sy = b.getInt("scrollY", 0);
1125                float scale = b.getFloat("scale", 1.0f);
1126                mDrawHistory = true;
1127                mHistoryPicture = p;
1128                mScrollX = sx;
1129                mScrollY = sy;
1130                mHistoryWidth = Math.round(p.getWidth() * scale);
1131                mHistoryHeight = Math.round(p.getHeight() * scale);
1132                // as getWidth() / getHeight() of the view are not
1133                // available yet, set up mActualScale, so that when
1134                // onSizeChanged() is called, the rest will be set
1135                // correctly
1136                mActualScale = scale;
1137                invalidate();
1138                return true;
1139            }
1140        }
1141        return false;
1142    }
1143
1144    /**
1145     * Restore the state of this WebView from the given map used in
1146     * {@link android.app.Activity#onRestoreInstanceState}. This method should
1147     * be called to restore the state of the WebView before using the object. If
1148     * it is called after the WebView has had a chance to build state (load
1149     * pages, create a back/forward list, etc.) there may be undesirable
1150     * side-effects. Please note that this method no longer restores the
1151     * display data for this WebView. See {@link #savePicture} and {@link
1152     * #restorePicture} for saving and restoring the display data.
1153     * @param inState The incoming Bundle of state.
1154     * @return The restored back/forward list or null if restoreState failed.
1155     * @see #savePicture
1156     * @see #restorePicture
1157     */
1158    public WebBackForwardList restoreState(Bundle inState) {
1159        WebBackForwardList returnList = null;
1160        if (inState == null) {
1161            return returnList;
1162        }
1163        if (inState.containsKey("index") && inState.containsKey("history")) {
1164            mCertificate = SslCertificate.restoreState(
1165                inState.getBundle("certificate"));
1166
1167            final WebBackForwardList list = mCallbackProxy.getBackForwardList();
1168            final int index = inState.getInt("index");
1169            // We can't use a clone of the list because we need to modify the
1170            // shared copy, so synchronize instead to prevent concurrent
1171            // modifications.
1172            synchronized (list) {
1173                final List<byte[]> history =
1174                        (List<byte[]>) inState.getSerializable("history");
1175                final int size = history.size();
1176                // Check the index bounds so we don't crash in native code while
1177                // restoring the history index.
1178                if (index < 0 || index >= size) {
1179                    return null;
1180                }
1181                for (int i = 0; i < size; i++) {
1182                    byte[] data = history.remove(0);
1183                    if (data == null) {
1184                        // If we somehow have null data, we cannot reconstruct
1185                        // the item and thus our history list cannot be rebuilt.
1186                        return null;
1187                    }
1188                    WebHistoryItem item = new WebHistoryItem(data);
1189                    list.addHistoryItem(item);
1190                }
1191                // Grab the most recent copy to return to the caller.
1192                returnList = copyBackForwardList();
1193                // Update the copy to have the correct index.
1194                returnList.setCurrentIndex(index);
1195            }
1196            // Remove all pending messages because we are restoring previous
1197            // state.
1198            mWebViewCore.removeMessages();
1199            // Send a restore state message.
1200            mWebViewCore.sendMessage(EventHub.RESTORE_STATE, index);
1201        }
1202        return returnList;
1203    }
1204
1205    /**
1206     * Load the given url.
1207     * @param url The url of the resource to load.
1208     */
1209    public void loadUrl(String url) {
1210        switchOutDrawHistory();
1211        mWebViewCore.sendMessage(EventHub.LOAD_URL, url);
1212        clearTextEntry();
1213    }
1214
1215    /**
1216     * Load the given data into the WebView. This will load the data into
1217     * WebView using the data: scheme. Content loaded through this mechanism
1218     * does not have the ability to load content from the network.
1219     * @param data A String of data in the given encoding.
1220     * @param mimeType The MIMEType of the data. i.e. text/html, image/jpeg
1221     * @param encoding The encoding of the data. i.e. utf-8, base64
1222     */
1223    public void loadData(String data, String mimeType, String encoding) {
1224        loadUrl("data:" + mimeType + ";" + encoding + "," + data);
1225    }
1226
1227    /**
1228     * Load the given data into the WebView, use the provided URL as the base
1229     * URL for the content. The base URL is the URL that represents the page
1230     * that is loaded through this interface. As such, it is used for the
1231     * history entry and to resolve any relative URLs. The failUrl is used if
1232     * browser fails to load the data provided. If it is empty or null, and the
1233     * load fails, then no history entry is created.
1234     * <p>
1235     * Note for post 1.0. Due to the change in the WebKit, the access to asset
1236     * files through "file:///android_asset/" for the sub resources is more
1237     * restricted. If you provide null or empty string as baseUrl, you won't be
1238     * able to access asset files. If the baseUrl is anything other than
1239     * http(s)/ftp(s)/about/javascript as scheme, you can access asset files for
1240     * sub resources.
1241     *
1242     * @param baseUrl Url to resolve relative paths with, if null defaults to
1243     *            "about:blank"
1244     * @param data A String of data in the given encoding.
1245     * @param mimeType The MIMEType of the data. i.e. text/html. If null,
1246     *            defaults to "text/html"
1247     * @param encoding The encoding of the data. i.e. utf-8, us-ascii
1248     * @param failUrl URL to use if the content fails to load or null.
1249     */
1250    public void loadDataWithBaseURL(String baseUrl, String data,
1251            String mimeType, String encoding, String failUrl) {
1252
1253        if (baseUrl != null && baseUrl.toLowerCase().startsWith("data:")) {
1254            loadData(data, mimeType, encoding);
1255            return;
1256        }
1257        switchOutDrawHistory();
1258        HashMap arg = new HashMap();
1259        arg.put("baseUrl", baseUrl);
1260        arg.put("data", data);
1261        arg.put("mimeType", mimeType);
1262        arg.put("encoding", encoding);
1263        arg.put("failUrl", failUrl);
1264        mWebViewCore.sendMessage(EventHub.LOAD_DATA, arg);
1265        clearTextEntry();
1266    }
1267
1268    /**
1269     * Stop the current load.
1270     */
1271    public void stopLoading() {
1272        // TODO: should we clear all the messages in the queue before sending
1273        // STOP_LOADING?
1274        switchOutDrawHistory();
1275        mWebViewCore.sendMessage(EventHub.STOP_LOADING);
1276    }
1277
1278    /**
1279     * Reload the current url.
1280     */
1281    public void reload() {
1282        switchOutDrawHistory();
1283        mWebViewCore.sendMessage(EventHub.RELOAD);
1284    }
1285
1286    /**
1287     * Return true if this WebView has a back history item.
1288     * @return True iff this WebView has a back history item.
1289     */
1290    public boolean canGoBack() {
1291        WebBackForwardList l = mCallbackProxy.getBackForwardList();
1292        synchronized (l) {
1293            if (l.getClearPending()) {
1294                return false;
1295            } else {
1296                return l.getCurrentIndex() > 0;
1297            }
1298        }
1299    }
1300
1301    /**
1302     * Go back in the history of this WebView.
1303     */
1304    public void goBack() {
1305        goBackOrForward(-1);
1306    }
1307
1308    /**
1309     * Return true if this WebView has a forward history item.
1310     * @return True iff this Webview has a forward history item.
1311     */
1312    public boolean canGoForward() {
1313        WebBackForwardList l = mCallbackProxy.getBackForwardList();
1314        synchronized (l) {
1315            if (l.getClearPending()) {
1316                return false;
1317            } else {
1318                return l.getCurrentIndex() < l.getSize() - 1;
1319            }
1320        }
1321    }
1322
1323    /**
1324     * Go forward in the history of this WebView.
1325     */
1326    public void goForward() {
1327        goBackOrForward(1);
1328    }
1329
1330    /**
1331     * Return true if the page can go back or forward the given
1332     * number of steps.
1333     * @param steps The negative or positive number of steps to move the
1334     *              history.
1335     */
1336    public boolean canGoBackOrForward(int steps) {
1337        WebBackForwardList l = mCallbackProxy.getBackForwardList();
1338        synchronized (l) {
1339            if (l.getClearPending()) {
1340                return false;
1341            } else {
1342                int newIndex = l.getCurrentIndex() + steps;
1343                return newIndex >= 0 && newIndex < l.getSize();
1344            }
1345        }
1346    }
1347
1348    /**
1349     * Go to the history item that is the number of steps away from
1350     * the current item. Steps is negative if backward and positive
1351     * if forward.
1352     * @param steps The number of steps to take back or forward in the back
1353     *              forward list.
1354     */
1355    public void goBackOrForward(int steps) {
1356        goBackOrForward(steps, false);
1357    }
1358
1359    private void goBackOrForward(int steps, boolean ignoreSnapshot) {
1360        // every time we go back or forward, we want to reset the
1361        // WebView certificate:
1362        // if the new site is secure, we will reload it and get a
1363        // new certificate set;
1364        // if the new site is not secure, the certificate must be
1365        // null, and that will be the case
1366        mCertificate = null;
1367        if (steps != 0) {
1368            clearTextEntry();
1369            mWebViewCore.sendMessage(EventHub.GO_BACK_FORWARD, steps,
1370                    ignoreSnapshot ? 1 : 0);
1371        }
1372    }
1373
1374    private boolean extendScroll(int y) {
1375        int finalY = mScroller.getFinalY();
1376        int newY = pinLocY(finalY + y);
1377        if (newY == finalY) return false;
1378        mScroller.setFinalY(newY);
1379        mScroller.extendDuration(computeDuration(0, y));
1380        return true;
1381    }
1382
1383    /**
1384     * Scroll the contents of the view up by half the view size
1385     * @param top true to jump to the top of the page
1386     * @return true if the page was scrolled
1387     */
1388    public boolean pageUp(boolean top) {
1389        if (mNativeClass == 0) {
1390            return false;
1391        }
1392        nativeClearFocus(-1, -1);
1393        if (top) {
1394            // go to the top of the document
1395            return pinScrollTo(mScrollX, 0, true, 0);
1396        }
1397        // Page up
1398        int h = getHeight();
1399        int y;
1400        if (h > 2 * PAGE_SCROLL_OVERLAP) {
1401            y = -h + PAGE_SCROLL_OVERLAP;
1402        } else {
1403            y = -h / 2;
1404        }
1405        mUserScroll = true;
1406        return mScroller.isFinished() ? pinScrollBy(0, y, true, 0)
1407                : extendScroll(y);
1408    }
1409
1410    /**
1411     * Scroll the contents of the view down by half the page size
1412     * @param bottom true to jump to bottom of page
1413     * @return true if the page was scrolled
1414     */
1415    public boolean pageDown(boolean bottom) {
1416        if (mNativeClass == 0) {
1417            return false;
1418        }
1419        nativeClearFocus(-1, -1);
1420        if (bottom) {
1421            return pinScrollTo(mScrollX, mContentHeight, true, 0);
1422        }
1423        // Page down.
1424        int h = getHeight();
1425        int y;
1426        if (h > 2 * PAGE_SCROLL_OVERLAP) {
1427            y = h - PAGE_SCROLL_OVERLAP;
1428        } else {
1429            y = h / 2;
1430        }
1431        mUserScroll = true;
1432        return mScroller.isFinished() ? pinScrollBy(0, y, true, 0)
1433                : extendScroll(y);
1434    }
1435
1436    /**
1437     * Clear the view so that onDraw() will draw nothing but white background,
1438     * and onMeasure() will return 0 if MeasureSpec is not MeasureSpec.EXACTLY
1439     */
1440    public void clearView() {
1441        mContentWidth = 0;
1442        mContentHeight = 0;
1443        mWebViewCore.sendMessage(EventHub.CLEAR_CONTENT);
1444    }
1445
1446    /**
1447     * Return a new picture that captures the current display of the webview.
1448     * This is a copy of the display, and will be unaffected if the webview
1449     * later loads a different URL.
1450     *
1451     * @return a picture containing the current contents of the view. Note this
1452     *         picture is of the entire document, and is not restricted to the
1453     *         bounds of the view.
1454     */
1455    public Picture capturePicture() {
1456        if (null == mWebViewCore) return null; // check for out of memory tab
1457        return mWebViewCore.copyContentPicture();
1458    }
1459
1460    /**
1461     *  Return true if the browser is displaying a TextView for text input.
1462     */
1463    private boolean inEditingMode() {
1464        return mTextEntry != null && mTextEntry.getParent() != null
1465                && mTextEntry.hasFocus();
1466    }
1467
1468    private void clearTextEntry() {
1469        if (inEditingMode()) {
1470            mTextEntry.remove();
1471        }
1472    }
1473
1474    /**
1475     * Return the current scale of the WebView
1476     * @return The current scale.
1477     */
1478    public float getScale() {
1479        return mActualScale;
1480    }
1481
1482    /**
1483     * Set the initial scale for the WebView. 0 means default. If
1484     * {@link WebSettings#getUseWideViewPort()} is true, it zooms out all the
1485     * way. Otherwise it starts with 100%. If initial scale is greater than 0,
1486     * WebView starts will this value as initial scale.
1487     *
1488     * @param scaleInPercent The initial scale in percent.
1489     */
1490    public void setInitialScale(int scaleInPercent) {
1491        mInitialScale = scaleInPercent;
1492    }
1493
1494    /**
1495     * Invoke the graphical zoom picker widget for this WebView. This will
1496     * result in the zoom widget appearing on the screen to control the zoom
1497     * level of this WebView.
1498     */
1499    public void invokeZoomPicker() {
1500        if (!getSettings().supportZoom()) {
1501            Log.w(LOGTAG, "This WebView doesn't support zoom.");
1502            return;
1503        }
1504        clearTextEntry();
1505        if (getSettings().getBuiltInZoomControls()) {
1506            mZoomButtonsController.setVisible(true);
1507        } else {
1508            mPrivateHandler.removeCallbacks(mZoomControlRunnable);
1509            mPrivateHandler.postDelayed(mZoomControlRunnable,
1510                    ZOOM_CONTROLS_TIMEOUT);
1511        }
1512    }
1513
1514    /**
1515     * Return a HitTestResult based on the current focus node. If a HTML::a tag
1516     * is found and the anchor has a non-javascript url, the HitTestResult type
1517     * is set to SRC_ANCHOR_TYPE and the url is set in the "extra" field. If the
1518     * anchor does not have a url or if it is a javascript url, the type will
1519     * be UNKNOWN_TYPE and the url has to be retrieved through
1520     * {@link #requestFocusNodeHref} asynchronously. If a HTML::img tag is
1521     * found, the HitTestResult type is set to IMAGE_TYPE and the url is set in
1522     * the "extra" field. A type of
1523     * SRC_IMAGE_ANCHOR_TYPE indicates an anchor with a url that has an image as
1524     * a child node. If a phone number is found, the HitTestResult type is set
1525     * to PHONE_TYPE and the phone number is set in the "extra" field of
1526     * HitTestResult. If a map address is found, the HitTestResult type is set
1527     * to GEO_TYPE and the address is set in the "extra" field of HitTestResult.
1528     * If an email address is found, the HitTestResult type is set to EMAIL_TYPE
1529     * and the email is set in the "extra" field of HitTestResult. Otherwise,
1530     * HitTestResult type is set to UNKNOWN_TYPE.
1531     */
1532    public HitTestResult getHitTestResult() {
1533        if (mNativeClass == 0) {
1534            return null;
1535        }
1536
1537        HitTestResult result = new HitTestResult();
1538
1539        if (nativeUpdateFocusNode()) {
1540            FocusNode node = mFocusNode;
1541            if (node.mIsTextField || node.mIsTextArea) {
1542                result.setType(HitTestResult.EDIT_TEXT_TYPE);
1543            } else if (node.mText != null) {
1544                String text = node.mText;
1545                if (text.startsWith(SCHEME_TEL)) {
1546                    result.setType(HitTestResult.PHONE_TYPE);
1547                    result.setExtra(text.substring(SCHEME_TEL.length()));
1548                } else if (text.startsWith(SCHEME_MAILTO)) {
1549                    result.setType(HitTestResult.EMAIL_TYPE);
1550                    result.setExtra(text.substring(SCHEME_MAILTO.length()));
1551                } else if (text.startsWith(SCHEME_GEO)) {
1552                    result.setType(HitTestResult.GEO_TYPE);
1553                    result.setExtra(URLDecoder.decode(text
1554                            .substring(SCHEME_GEO.length())));
1555                } else if (node.mIsAnchor) {
1556                    result.setType(HitTestResult.SRC_ANCHOR_TYPE);
1557                    result.setExtra(text);
1558                }
1559            }
1560        }
1561        int type = result.getType();
1562        if (type == HitTestResult.UNKNOWN_TYPE
1563                || type == HitTestResult.SRC_ANCHOR_TYPE) {
1564            // Now check to see if it is an image.
1565            int contentX = viewToContent((int) mLastTouchX + mScrollX);
1566            int contentY = viewToContent((int) mLastTouchY + mScrollY);
1567            String text = nativeImageURI(contentX, contentY);
1568            if (text != null) {
1569                result.setType(type == HitTestResult.UNKNOWN_TYPE ?
1570                        HitTestResult.IMAGE_TYPE :
1571                        HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
1572                result.setExtra(text);
1573            }
1574        }
1575        return result;
1576    }
1577
1578    /**
1579     * Request the href of an anchor element due to getFocusNodePath returning
1580     * "href." If hrefMsg is null, this method returns immediately and does not
1581     * dispatch hrefMsg to its target.
1582     *
1583     * @param hrefMsg This message will be dispatched with the result of the
1584     *            request as the data member with "url" as key. The result can
1585     *            be null.
1586     */
1587    public void requestFocusNodeHref(Message hrefMsg) {
1588        if (hrefMsg == null || mNativeClass == 0) {
1589            return;
1590        }
1591        if (nativeUpdateFocusNode()) {
1592            FocusNode node = mFocusNode;
1593            if (node.mIsAnchor) {
1594                // NOTE: We may already have the url of the anchor stored in
1595                // node.mText but it may be out of date or the caller may want
1596                // to know about javascript urls.
1597                mWebViewCore.sendMessage(EventHub.REQUEST_FOCUS_HREF,
1598                        node.mFramePointer, node.mNodePointer, hrefMsg);
1599            }
1600        }
1601    }
1602
1603    /**
1604     * Request the url of the image last touched by the user. msg will be sent
1605     * to its target with a String representing the url as its object.
1606     *
1607     * @param msg This message will be dispatched with the result of the request
1608     *            as the data member with "url" as key. The result can be null.
1609     */
1610    public void requestImageRef(Message msg) {
1611        int contentX = viewToContent((int) mLastTouchX + mScrollX);
1612        int contentY = viewToContent((int) mLastTouchY + mScrollY);
1613        String ref = nativeImageURI(contentX, contentY);
1614        Bundle data = msg.getData();
1615        data.putString("url", ref);
1616        msg.setData(data);
1617        msg.sendToTarget();
1618    }
1619
1620    private static int pinLoc(int x, int viewMax, int docMax) {
1621//        Log.d(LOGTAG, "-- pinLoc " + x + " " + viewMax + " " + docMax);
1622        if (docMax < viewMax) {   // the doc has room on the sides for "blank"
1623            // pin the short document to the top/left of the screen
1624            x = 0;
1625//            Log.d(LOGTAG, "--- center " + x);
1626        } else if (x < 0) {
1627            x = 0;
1628//            Log.d(LOGTAG, "--- zero");
1629        } else if (x + viewMax > docMax) {
1630            x = docMax - viewMax;
1631//            Log.d(LOGTAG, "--- pin " + x);
1632        }
1633        return x;
1634    }
1635
1636    // Expects x in view coordinates
1637    private int pinLocX(int x) {
1638        return pinLoc(x, getViewWidth(), computeHorizontalScrollRange());
1639    }
1640
1641    // Expects y in view coordinates
1642    private int pinLocY(int y) {
1643        return pinLoc(y, getViewHeight(), computeVerticalScrollRange());
1644    }
1645
1646    /*package*/ int viewToContent(int x) {
1647        return Math.round(x * mInvActualScale);
1648    }
1649
1650    private int contentToView(int x) {
1651        return Math.round(x * mActualScale);
1652    }
1653
1654    // Called by JNI to invalidate the View, given rectangle coordinates in
1655    // content space
1656    private void viewInvalidate(int l, int t, int r, int b) {
1657        invalidate(contentToView(l), contentToView(t), contentToView(r),
1658                contentToView(b));
1659    }
1660
1661    // Called by JNI to invalidate the View after a delay, given rectangle
1662    // coordinates in content space
1663    private void viewInvalidateDelayed(long delay, int l, int t, int r, int b) {
1664        postInvalidateDelayed(delay, contentToView(l), contentToView(t),
1665                contentToView(r), contentToView(b));
1666    }
1667
1668    private Rect contentToView(Rect x) {
1669        return new Rect(contentToView(x.left), contentToView(x.top)
1670                , contentToView(x.right), contentToView(x.bottom));
1671    }
1672
1673    /* call from webcoreview.draw(), so we're still executing in the UI thread
1674    */
1675    private void recordNewContentSize(int w, int h, boolean updateLayout) {
1676
1677        // premature data from webkit, ignore
1678        if ((w | h) == 0) {
1679            return;
1680        }
1681
1682        // don't abort a scroll animation if we didn't change anything
1683        if (mContentWidth != w || mContentHeight != h) {
1684            // record new dimensions
1685            mContentWidth = w;
1686            mContentHeight = h;
1687            // If history Picture is drawn, don't update scroll. They will be
1688            // updated when we get out of that mode.
1689            if (!mDrawHistory) {
1690                // repin our scroll, taking into account the new content size
1691                int oldX = mScrollX;
1692                int oldY = mScrollY;
1693                mScrollX = pinLocX(mScrollX);
1694                mScrollY = pinLocY(mScrollY);
1695                // android.util.Log.d("skia", "recordNewContentSize -
1696                // abortAnimation");
1697                mScroller.abortAnimation(); // just in case
1698                if (oldX != mScrollX || oldY != mScrollY) {
1699                    sendOurVisibleRect();
1700                }
1701            }
1702        }
1703        contentSizeChanged(updateLayout);
1704    }
1705
1706    private void setNewZoomScale(float scale, boolean force) {
1707        if (scale < mMinZoomScale) {
1708            scale = mMinZoomScale;
1709        } else if (scale > mMaxZoomScale) {
1710            scale = mMaxZoomScale;
1711        }
1712        if (scale != mActualScale || force) {
1713            if (mDrawHistory) {
1714                // If history Picture is drawn, don't update scroll. They will
1715                // be updated when we get out of that mode.
1716                if (scale != mActualScale && !mPreviewZoomOnly) {
1717                    mCallbackProxy.onScaleChanged(mActualScale, scale);
1718                }
1719                mActualScale = scale;
1720                mInvActualScale = 1 / scale;
1721                if (!mPreviewZoomOnly) {
1722                    sendViewSizeZoom();
1723                }
1724            } else {
1725                // update our scroll so we don't appear to jump
1726                // i.e. keep the center of the doc in the center of the view
1727
1728                int oldX = mScrollX;
1729                int oldY = mScrollY;
1730                float ratio = scale * mInvActualScale;   // old inverse
1731                float sx = ratio * oldX + (ratio - 1) * mZoomCenterX;
1732                float sy = ratio * oldY + (ratio - 1) * mZoomCenterY;
1733
1734                // now update our new scale and inverse
1735                if (scale != mActualScale && !mPreviewZoomOnly) {
1736                    mCallbackProxy.onScaleChanged(mActualScale, scale);
1737                }
1738                mActualScale = scale;
1739                mInvActualScale = 1 / scale;
1740
1741                // as we don't have animation for scaling, don't do animation
1742                // for scrolling, as it causes weird intermediate state
1743                //        pinScrollTo(Math.round(sx), Math.round(sy));
1744                mScrollX = pinLocX(Math.round(sx));
1745                mScrollY = pinLocY(Math.round(sy));
1746
1747                if (!mPreviewZoomOnly) {
1748                    sendViewSizeZoom();
1749                    sendOurVisibleRect();
1750                }
1751            }
1752        }
1753    }
1754
1755    // Used to avoid sending many visible rect messages.
1756    private Rect mLastVisibleRectSent;
1757    private Rect mLastGlobalRect;
1758
1759    private Rect sendOurVisibleRect() {
1760        Rect rect = new Rect();
1761        calcOurContentVisibleRect(rect);
1762        if (mFindIsUp) {
1763            rect.bottom -= viewToContent(FIND_HEIGHT);
1764        }
1765        // Rect.equals() checks for null input.
1766        if (!rect.equals(mLastVisibleRectSent)) {
1767            mWebViewCore.sendMessage(EventHub.SET_SCROLL_OFFSET,
1768                                     rect.left, rect.top);
1769            mLastVisibleRectSent = rect;
1770        }
1771        Rect globalRect = new Rect();
1772        if (getGlobalVisibleRect(globalRect)
1773                && !globalRect.equals(mLastGlobalRect)) {
1774            // TODO: the global offset is only used by windowRect()
1775            // in ChromeClientAndroid ; other clients such as touch
1776            // and mouse events could return view + screen relative points.
1777            mWebViewCore.sendMessage(EventHub.SET_GLOBAL_BOUNDS, globalRect);
1778            mLastGlobalRect = globalRect;
1779        }
1780        return rect;
1781    }
1782
1783    // Sets r to be the visible rectangle of our webview in view coordinates
1784    private void calcOurVisibleRect(Rect r) {
1785        Point p = new Point();
1786        getGlobalVisibleRect(r, p);
1787        r.offset(-p.x, -p.y);
1788    }
1789
1790    // Sets r to be our visible rectangle in content coordinates
1791    private void calcOurContentVisibleRect(Rect r) {
1792        calcOurVisibleRect(r);
1793        r.left = viewToContent(r.left);
1794        r.top = viewToContent(r.top);
1795        r.right = viewToContent(r.right);
1796        r.bottom = viewToContent(r.bottom);
1797    }
1798
1799    /**
1800     * Compute unzoomed width and height, and if they differ from the last
1801     * values we sent, send them to webkit (to be used has new viewport)
1802     *
1803     * @return true if new values were sent
1804     */
1805    private boolean sendViewSizeZoom() {
1806        int newWidth = Math.round(getViewWidth() * mInvActualScale);
1807        int newHeight = Math.round(getViewHeight() * mInvActualScale);
1808        /*
1809         * Because the native side may have already done a layout before the
1810         * View system was able to measure us, we have to send a height of 0 to
1811         * remove excess whitespace when we grow our width. This will trigger a
1812         * layout and a change in content size. This content size change will
1813         * mean that contentSizeChanged will either call this method directly or
1814         * indirectly from onSizeChanged.
1815         */
1816        if (newWidth > mLastWidthSent && mWrapContent) {
1817            newHeight = 0;
1818        }
1819        // Avoid sending another message if the dimensions have not changed.
1820        if (newWidth != mLastWidthSent || newHeight != mLastHeightSent) {
1821            mWebViewCore.sendMessage(EventHub.VIEW_SIZE_CHANGED,
1822                    newWidth, newHeight, new Float(mActualScale));
1823            mLastWidthSent = newWidth;
1824            mLastHeightSent = newHeight;
1825            return true;
1826        }
1827        return false;
1828    }
1829
1830    @Override
1831    protected int computeHorizontalScrollRange() {
1832        if (mDrawHistory) {
1833            return mHistoryWidth;
1834        } else {
1835            return contentToView(mContentWidth);
1836        }
1837    }
1838
1839    // Make sure this stays in sync with the actual height of the FindDialog.
1840    private static final int FIND_HEIGHT = 79;
1841
1842    @Override
1843    protected int computeVerticalScrollRange() {
1844        if (mDrawHistory) {
1845            return mHistoryHeight;
1846        } else {
1847            int height = contentToView(mContentHeight);
1848            if (mFindIsUp) {
1849                height += FIND_HEIGHT;
1850            }
1851            return height;
1852        }
1853    }
1854
1855    /**
1856     * Get the url for the current page. This is not always the same as the url
1857     * passed to WebViewClient.onPageStarted because although the load for
1858     * that url has begun, the current page may not have changed.
1859     * @return The url for the current page.
1860     */
1861    public String getUrl() {
1862        WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
1863        return h != null ? h.getUrl() : null;
1864    }
1865
1866    /**
1867     * Get the original url for the current page. This is not always the same
1868     * as the url passed to WebViewClient.onPageStarted because although the
1869     * load for that url has begun, the current page may not have changed.
1870     * Also, there may have been redirects resulting in a different url to that
1871     * originally requested.
1872     * @return The url that was originally requested for the current page.
1873     */
1874    public String getOriginalUrl() {
1875        WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
1876        return h != null ? h.getOriginalUrl() : null;
1877    }
1878
1879    /**
1880     * Get the title for the current page. This is the title of the current page
1881     * until WebViewClient.onReceivedTitle is called.
1882     * @return The title for the current page.
1883     */
1884    public String getTitle() {
1885        WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
1886        return h != null ? h.getTitle() : null;
1887    }
1888
1889    /**
1890     * Get the favicon for the current page. This is the favicon of the current
1891     * page until WebViewClient.onReceivedIcon is called.
1892     * @return The favicon for the current page.
1893     */
1894    public Bitmap getFavicon() {
1895        WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
1896        return h != null ? h.getFavicon() : null;
1897    }
1898
1899    /**
1900     * Get the progress for the current page.
1901     * @return The progress for the current page between 0 and 100.
1902     */
1903    public int getProgress() {
1904        return mCallbackProxy.getProgress();
1905    }
1906
1907    /**
1908     * @return the height of the HTML content.
1909     */
1910    public int getContentHeight() {
1911        return mContentHeight;
1912    }
1913
1914    /**
1915     * Pause all layout, parsing, and javascript timers. This can be useful if
1916     * the WebView is not visible or the application has been paused.
1917     */
1918    public void pauseTimers() {
1919        mWebViewCore.sendMessage(EventHub.PAUSE_TIMERS);
1920    }
1921
1922    /**
1923     * Resume all layout, parsing, and javascript timers. This will resume
1924     * dispatching all timers.
1925     */
1926    public void resumeTimers() {
1927        mWebViewCore.sendMessage(EventHub.RESUME_TIMERS);
1928    }
1929
1930    /**
1931     * Clear the resource cache. Note that the cache is per-application, so
1932     * this will clear the cache for all WebViews used.
1933     *
1934     * @param includeDiskFiles If false, only the RAM cache is cleared.
1935     */
1936    public void clearCache(boolean includeDiskFiles) {
1937        // Note: this really needs to be a static method as it clears cache for all
1938        // WebView. But we need mWebViewCore to send message to WebCore thread, so
1939        // we can't make this static.
1940        mWebViewCore.sendMessage(EventHub.CLEAR_CACHE,
1941                includeDiskFiles ? 1 : 0, 0);
1942    }
1943
1944    /**
1945     * Make sure that clearing the form data removes the adapter from the
1946     * currently focused textfield if there is one.
1947     */
1948    public void clearFormData() {
1949        if (inEditingMode()) {
1950            AutoCompleteAdapter adapter = null;
1951            mTextEntry.setAdapterCustom(adapter);
1952        }
1953    }
1954
1955    /**
1956     * Tell the WebView to clear its internal back/forward list.
1957     */
1958    public void clearHistory() {
1959        mCallbackProxy.getBackForwardList().setClearPending();
1960        mWebViewCore.sendMessage(EventHub.CLEAR_HISTORY);
1961    }
1962
1963    /**
1964     * Clear the SSL preferences table stored in response to proceeding with SSL
1965     * certificate errors.
1966     */
1967    public void clearSslPreferences() {
1968        mWebViewCore.sendMessage(EventHub.CLEAR_SSL_PREF_TABLE);
1969    }
1970
1971    /**
1972     * Return the WebBackForwardList for this WebView. This contains the
1973     * back/forward list for use in querying each item in the history stack.
1974     * This is a copy of the private WebBackForwardList so it contains only a
1975     * snapshot of the current state. Multiple calls to this method may return
1976     * different objects. The object returned from this method will not be
1977     * updated to reflect any new state.
1978     */
1979    public WebBackForwardList copyBackForwardList() {
1980        return mCallbackProxy.getBackForwardList().clone();
1981    }
1982
1983    /*
1984     * Highlight and scroll to the next occurance of String in findAll.
1985     * Wraps the page infinitely, and scrolls.  Must be called after
1986     * calling findAll.
1987     *
1988     * @param forward Direction to search.
1989     */
1990    public void findNext(boolean forward) {
1991        nativeFindNext(forward);
1992    }
1993
1994    /*
1995     * Find all instances of find on the page and highlight them.
1996     * @param find  String to find.
1997     * @return int  The number of occurances of the String "find"
1998     *              that were found.
1999     */
2000    public int findAll(String find) {
2001        mFindIsUp = true;
2002        int result = nativeFindAll(find.toLowerCase(), find.toUpperCase());
2003        invalidate();
2004        return result;
2005    }
2006
2007    // Used to know whether the find dialog is open.  Affects whether
2008    // or not we draw the highlights for matches.
2009    private boolean mFindIsUp;
2010
2011    private native int nativeFindAll(String findLower, String findUpper);
2012    private native void nativeFindNext(boolean forward);
2013
2014    /**
2015     * Return the first substring consisting of the address of a physical
2016     * location. Currently, only addresses in the United States are detected,
2017     * and consist of:
2018     * - a house number
2019     * - a street name
2020     * - a street type (Road, Circle, etc), either spelled out or abbreviated
2021     * - a city name
2022     * - a state or territory, either spelled out or two-letter abbr.
2023     * - an optional 5 digit or 9 digit zip code.
2024     *
2025     * All names must be correctly capitalized, and the zip code, if present,
2026     * must be valid for the state. The street type must be a standard USPS
2027     * spelling or abbreviation. The state or territory must also be spelled
2028     * or abbreviated using USPS standards. The house number may not exceed
2029     * five digits.
2030     * @param addr The string to search for addresses.
2031     *
2032     * @return the address, or if no address is found, return null.
2033     */
2034    public static String findAddress(String addr) {
2035        return WebViewCore.nativeFindAddress(addr);
2036    }
2037
2038    /*
2039     * Clear the highlighting surrounding text matches created by findAll.
2040     */
2041    public void clearMatches() {
2042        mFindIsUp = false;
2043        nativeSetFindIsDown();
2044        // Now that the dialog has been removed, ensure that we scroll to a
2045        // location that is not beyond the end of the page.
2046        pinScrollTo(mScrollX, mScrollY, false, 0);
2047        invalidate();
2048    }
2049
2050    /**
2051     * Query the document to see if it contains any image references. The
2052     * message object will be dispatched with arg1 being set to 1 if images
2053     * were found and 0 if the document does not reference any images.
2054     * @param response The message that will be dispatched with the result.
2055     */
2056    public void documentHasImages(Message response) {
2057        if (response == null) {
2058            return;
2059        }
2060        mWebViewCore.sendMessage(EventHub.DOC_HAS_IMAGES, response);
2061    }
2062
2063    @Override
2064    public void computeScroll() {
2065        if (mScroller.computeScrollOffset()) {
2066            int oldX = mScrollX;
2067            int oldY = mScrollY;
2068            mScrollX = mScroller.getCurrX();
2069            mScrollY = mScroller.getCurrY();
2070            postInvalidate();  // So we draw again
2071            if (oldX != mScrollX || oldY != mScrollY) {
2072                // as onScrollChanged() is not called, sendOurVisibleRect()
2073                // needs to be call explicitly
2074                sendOurVisibleRect();
2075            }
2076        } else {
2077            super.computeScroll();
2078        }
2079    }
2080
2081    private static int computeDuration(int dx, int dy) {
2082        int distance = Math.max(Math.abs(dx), Math.abs(dy));
2083        int duration = distance * 1000 / STD_SPEED;
2084        return Math.min(duration, MAX_DURATION);
2085    }
2086
2087    // helper to pin the scrollBy parameters (already in view coordinates)
2088    // returns true if the scroll was changed
2089    private boolean pinScrollBy(int dx, int dy, boolean animate, int animationDuration) {
2090        return pinScrollTo(mScrollX + dx, mScrollY + dy, animate, animationDuration);
2091    }
2092
2093    // helper to pin the scrollTo parameters (already in view coordinates)
2094    // returns true if the scroll was changed
2095    private boolean pinScrollTo(int x, int y, boolean animate, int animationDuration) {
2096        x = pinLocX(x);
2097        y = pinLocY(y);
2098        int dx = x - mScrollX;
2099        int dy = y - mScrollY;
2100
2101        if ((dx | dy) == 0) {
2102            return false;
2103        }
2104
2105        if (true && animate) {
2106            //        Log.d(LOGTAG, "startScroll: " + dx + " " + dy);
2107
2108            mScroller.startScroll(mScrollX, mScrollY, dx, dy,
2109                    animationDuration > 0 ? animationDuration : computeDuration(dx, dy));
2110            invalidate();
2111        } else {
2112            mScroller.abortAnimation(); // just in case
2113            scrollTo(x, y);
2114        }
2115        return true;
2116    }
2117
2118    // Scale from content to view coordinates, and pin.
2119    // Also called by jni webview.cpp
2120    private void setContentScrollBy(int cx, int cy, boolean animate) {
2121        if (mDrawHistory) {
2122            // disallow WebView to change the scroll position as History Picture
2123            // is used in the view system.
2124            // TODO: as we switchOutDrawHistory when trackball or navigation
2125            // keys are hit, this should be safe. Right?
2126            return;
2127        }
2128        cx = contentToView(cx);
2129        cy = contentToView(cy);
2130        if (mHeightCanMeasure) {
2131            // move our visible rect according to scroll request
2132            if (cy != 0) {
2133                Rect tempRect = new Rect();
2134                calcOurVisibleRect(tempRect);
2135                tempRect.offset(cx, cy);
2136                requestRectangleOnScreen(tempRect);
2137            }
2138            // FIXME: We scroll horizontally no matter what because currently
2139            // ScrollView and ListView will not scroll horizontally.
2140            // FIXME: Why do we only scroll horizontally if there is no
2141            // vertical scroll?
2142//                Log.d(LOGTAG, "setContentScrollBy cy=" + cy);
2143            if (cy == 0 && cx != 0) {
2144                pinScrollBy(cx, 0, animate, 0);
2145            }
2146        } else {
2147            pinScrollBy(cx, cy, animate, 0);
2148        }
2149    }
2150
2151    // scale from content to view coordinates, and pin
2152    // return true if pin caused the final x/y different than the request cx/cy;
2153    // return false if the view scroll to the exact position as it is requested.
2154    private boolean setContentScrollTo(int cx, int cy) {
2155        if (mDrawHistory) {
2156            // disallow WebView to change the scroll position as History Picture
2157            // is used in the view system.
2158            // One known case where this is called is that WebCore tries to
2159            // restore the scroll position. As history Picture already uses the
2160            // saved scroll position, it is ok to skip this.
2161            return false;
2162        }
2163        int vx = contentToView(cx);
2164        int vy = contentToView(cy);
2165//        Log.d(LOGTAG, "content scrollTo [" + cx + " " + cy + "] view=[" +
2166//                      vx + " " + vy + "]");
2167        pinScrollTo(vx, vy, false, 0);
2168        if (mScrollX != vx || mScrollY != vy) {
2169            return true;
2170        } else {
2171            return false;
2172        }
2173    }
2174
2175    // scale from content to view coordinates, and pin
2176    private void spawnContentScrollTo(int cx, int cy) {
2177        if (mDrawHistory) {
2178            // disallow WebView to change the scroll position as History Picture
2179            // is used in the view system.
2180            return;
2181        }
2182        int vx = contentToView(cx);
2183        int vy = contentToView(cy);
2184        pinScrollTo(vx, vy, true, 0);
2185    }
2186
2187    /**
2188     * These are from webkit, and are in content coordinate system (unzoomed)
2189     */
2190    private void contentSizeChanged(boolean updateLayout) {
2191        // suppress 0,0 since we usually see real dimensions soon after
2192        // this avoids drawing the prev content in a funny place. If we find a
2193        // way to consolidate these notifications, this check may become
2194        // obsolete
2195        if ((mContentWidth | mContentHeight) == 0) {
2196            return;
2197        }
2198
2199        if (mHeightCanMeasure) {
2200            if (getMeasuredHeight() != contentToView(mContentHeight)
2201                    && updateLayout) {
2202                requestLayout();
2203            }
2204        } else if (mWidthCanMeasure) {
2205            if (getMeasuredWidth() != contentToView(mContentWidth)
2206                    && updateLayout) {
2207                requestLayout();
2208            }
2209        } else {
2210            // If we don't request a layout, try to send our view size to the
2211            // native side to ensure that WebCore has the correct dimensions.
2212            sendViewSizeZoom();
2213        }
2214    }
2215
2216    /**
2217     * Set the WebViewClient that will receive various notifications and
2218     * requests. This will replace the current handler.
2219     * @param client An implementation of WebViewClient.
2220     */
2221    public void setWebViewClient(WebViewClient client) {
2222        mCallbackProxy.setWebViewClient(client);
2223    }
2224
2225    /**
2226     * Register the interface to be used when content can not be handled by
2227     * the rendering engine, and should be downloaded instead. This will replace
2228     * the current handler.
2229     * @param listener An implementation of DownloadListener.
2230     */
2231    public void setDownloadListener(DownloadListener listener) {
2232        mCallbackProxy.setDownloadListener(listener);
2233    }
2234
2235    /**
2236     * Set the chrome handler. This is an implementation of WebChromeClient for
2237     * use in handling Javascript dialogs, favicons, titles, and the progress.
2238     * This will replace the current handler.
2239     * @param client An implementation of WebChromeClient.
2240     */
2241    public void setWebChromeClient(WebChromeClient client) {
2242        mCallbackProxy.setWebChromeClient(client);
2243    }
2244
2245    /**
2246     * Set the Picture listener. This is an interface used to receive
2247     * notifications of a new Picture.
2248     * @param listener An implementation of WebView.PictureListener.
2249     */
2250    public void setPictureListener(PictureListener listener) {
2251        mPictureListener = listener;
2252    }
2253
2254    /**
2255     * {@hide}
2256     */
2257    /* FIXME: Debug only! Remove for SDK! */
2258    public void externalRepresentation(Message callback) {
2259        mWebViewCore.sendMessage(EventHub.REQUEST_EXT_REPRESENTATION, callback);
2260    }
2261
2262    /**
2263     * {@hide}
2264     */
2265    /* FIXME: Debug only! Remove for SDK! */
2266    public void documentAsText(Message callback) {
2267        mWebViewCore.sendMessage(EventHub.REQUEST_DOC_AS_TEXT, callback);
2268    }
2269
2270    /**
2271     * Use this function to bind an object to Javascript so that the
2272     * methods can be accessed from Javascript.
2273     * <p><strong>IMPORTANT:</strong>
2274     * <ul>
2275     * <li> Using addJavascriptInterface() allows JavaScript to control your
2276     * application. This can be a very useful feature or a dangerous security
2277     * issue. When the HTML in the WebView is untrustworthy (for example, part
2278     * or all of the HTML is provided by some person or process), then an
2279     * attacker could inject HTML that will execute your code and possibly any
2280     * code of the attacker's choosing.<br>
2281     * Do not use addJavascriptInterface() unless all of the HTML in this
2282     * WebView was written by you.</li>
2283     * <li> The Java object that is bound runs in another thread and not in
2284     * the thread that it was constructed in.</li>
2285     * </ul></p>
2286     * @param obj The class instance to bind to Javascript
2287     * @param interfaceName The name to used to expose the class in Javascript
2288     */
2289    public void addJavascriptInterface(Object obj, String interfaceName) {
2290        // Use Hashmap rather than Bundle as Bundles can't cope with Objects
2291        HashMap arg = new HashMap();
2292        arg.put("object", obj);
2293        arg.put("interfaceName", interfaceName);
2294        mWebViewCore.sendMessage(EventHub.ADD_JS_INTERFACE, arg);
2295    }
2296
2297    /**
2298     * Return the WebSettings object used to control the settings for this
2299     * WebView.
2300     * @return A WebSettings object that can be used to control this WebView's
2301     *         settings.
2302     */
2303    public WebSettings getSettings() {
2304        return mWebViewCore.getSettings();
2305    }
2306
2307   /**
2308    * Return the list of currently loaded plugins.
2309    * @return The list of currently loaded plugins.
2310    */
2311    public static synchronized PluginList getPluginList() {
2312        if (sPluginList == null) {
2313            sPluginList = new PluginList();
2314        }
2315        return sPluginList;
2316    }
2317
2318   /**
2319    * Signal the WebCore thread to refresh its list of plugins. Use
2320    * this if the directory contents of one of the plugin directories
2321    * has been modified and needs its changes reflecting. May cause
2322    * plugin load and/or unload.
2323    * @param reloadOpenPages Set to true to reload all open pages.
2324    */
2325    public void refreshPlugins(boolean reloadOpenPages) {
2326        if (mWebViewCore != null) {
2327            mWebViewCore.sendMessage(EventHub.REFRESH_PLUGINS, reloadOpenPages);
2328        }
2329    }
2330
2331    //-------------------------------------------------------------------------
2332    // Override View methods
2333    //-------------------------------------------------------------------------
2334
2335    @Override
2336    protected void finalize() throws Throwable {
2337        destroy();
2338    }
2339
2340    @Override
2341    protected void onDraw(Canvas canvas) {
2342        // if mNativeClass is 0, the WebView has been destroyed. Do nothing.
2343        if (mNativeClass == 0) {
2344            return;
2345        }
2346        if (mWebViewCore.mEndScaleZoom) {
2347            mWebViewCore.mEndScaleZoom = false;
2348            if (mTouchMode >= FIRST_SCROLL_ZOOM
2349                    && mTouchMode <= LAST_SCROLL_ZOOM) {
2350                setHorizontalScrollBarEnabled(true);
2351                setVerticalScrollBarEnabled(true);
2352                mTouchMode = TOUCH_DONE_MODE;
2353            }
2354        }
2355        int sc = canvas.save();
2356        if (mTouchMode >= FIRST_SCROLL_ZOOM && mTouchMode <= LAST_SCROLL_ZOOM) {
2357            scrollZoomDraw(canvas);
2358        } else {
2359            nativeRecomputeFocus();
2360            // Update the buttons in the picture, so when we draw the picture
2361            // to the screen, they are in the correct state.
2362            // Tell the native side if user is a) touching the screen,
2363            // b) pressing the trackball down, or c) pressing the enter key
2364            // If the focus is a button, we need to draw it in the pressed
2365            // state.
2366            // If mNativeClass is 0, we should not reach here, so we do not
2367            // need to check it again.
2368            nativeRecordButtons(hasFocus() && hasWindowFocus(),
2369                    mTouchMode == TOUCH_SHORTPRESS_START_MODE
2370                    || mTrackballDown || mGotEnterDown, false);
2371            drawCoreAndFocusRing(canvas, mBackgroundColor, mDrawFocusRing);
2372        }
2373        canvas.restoreToCount(sc);
2374
2375        if (AUTO_REDRAW_HACK && mAutoRedraw) {
2376            invalidate();
2377        }
2378    }
2379
2380    @Override
2381    public void setLayoutParams(ViewGroup.LayoutParams params) {
2382        if (params.height == LayoutParams.WRAP_CONTENT) {
2383            mWrapContent = true;
2384        }
2385        super.setLayoutParams(params);
2386    }
2387
2388    @Override
2389    public boolean performLongClick() {
2390        if (inEditingMode()) {
2391            return mTextEntry.performLongClick();
2392        } else {
2393            return super.performLongClick();
2394        }
2395    }
2396
2397    private void drawCoreAndFocusRing(Canvas canvas, int color,
2398        boolean drawFocus) {
2399        if (mDrawHistory) {
2400            canvas.scale(mActualScale, mActualScale);
2401            canvas.drawPicture(mHistoryPicture);
2402            return;
2403        }
2404
2405        boolean animateZoom = mZoomScale != 0;
2406        boolean animateScroll = !mScroller.isFinished()
2407                || mVelocityTracker != null;
2408        if (animateZoom) {
2409            float zoomScale;
2410            int interval = (int) (SystemClock.uptimeMillis() - mZoomStart);
2411            if (interval < ZOOM_ANIMATION_LENGTH) {
2412                float ratio = (float) interval / ZOOM_ANIMATION_LENGTH;
2413                zoomScale = 1.0f / (mInvInitialZoomScale
2414                        + (mInvFinalZoomScale - mInvInitialZoomScale) * ratio);
2415                invalidate();
2416            } else {
2417                zoomScale = mZoomScale;
2418                // set mZoomScale to be 0 as we have done animation
2419                mZoomScale = 0;
2420            }
2421            float scale = (mActualScale - zoomScale) * mInvActualScale;
2422            float tx = scale * (mZoomCenterX + mScrollX);
2423            float ty = scale * (mZoomCenterY + mScrollY);
2424
2425            // this block pins the translate to "legal" bounds. This makes the
2426            // animation a bit non-obvious, but it means we won't pop when the
2427            // "real" zoom takes effect
2428            if (true) {
2429               // canvas.translate(mScrollX, mScrollY);
2430                tx -= mScrollX;
2431                ty -= mScrollY;
2432                tx = -pinLoc(-Math.round(tx), getViewWidth(), Math
2433                        .round(mContentWidth * zoomScale));
2434                ty = -pinLoc(-Math.round(ty), getViewHeight(), Math
2435                        .round(mContentHeight * zoomScale));
2436                tx += mScrollX;
2437                ty += mScrollY;
2438            }
2439            canvas.translate(tx, ty);
2440            canvas.scale(zoomScale, zoomScale);
2441        } else {
2442            canvas.scale(mActualScale, mActualScale);
2443        }
2444
2445        mWebViewCore.drawContentPicture(canvas, color, animateZoom,
2446                animateScroll);
2447
2448        if (mNativeClass == 0) return;
2449        if (mShiftIsPressed) {
2450            if (mTouchSelection) {
2451                nativeDrawSelectionRegion(canvas);
2452            } else {
2453                nativeDrawSelection(canvas, mSelectX, mSelectY,
2454                        mExtendSelection);
2455            }
2456        } else if (drawFocus) {
2457            if (mTouchMode == TOUCH_SHORTPRESS_START_MODE) {
2458                mTouchMode = TOUCH_SHORTPRESS_MODE;
2459                HitTestResult hitTest = getHitTestResult();
2460                if (hitTest != null &&
2461                        hitTest.mType != HitTestResult.UNKNOWN_TYPE) {
2462                    mPrivateHandler.sendMessageDelayed(mPrivateHandler
2463                            .obtainMessage(SWITCH_TO_LONGPRESS),
2464                            LONG_PRESS_TIMEOUT);
2465                }
2466            }
2467            nativeDrawFocusRing(canvas);
2468        }
2469        // When the FindDialog is up, only draw the matches if we are not in
2470        // the process of scrolling them into view.
2471        if (mFindIsUp && !animateScroll) {
2472            nativeDrawMatches(canvas);
2473        }
2474    }
2475
2476    private native void nativeDrawMatches(Canvas canvas);
2477
2478    private float scrollZoomGridScale(float invScale) {
2479        float griddedInvScale = (int) (invScale * SCROLL_ZOOM_GRID)
2480            / (float) SCROLL_ZOOM_GRID;
2481        return 1.0f / griddedInvScale;
2482    }
2483
2484    private float scrollZoomX(float scale) {
2485        int width = getViewWidth();
2486        float maxScrollZoomX = mContentWidth * scale - width;
2487        int maxX = mContentWidth - width;
2488        return -(maxScrollZoomX > 0 ? mZoomScrollX * maxScrollZoomX / maxX
2489                : maxScrollZoomX / 2);
2490    }
2491
2492    private float scrollZoomY(float scale) {
2493        int height = getViewHeight();
2494        float maxScrollZoomY = mContentHeight * scale - height;
2495        int maxY = mContentHeight - height;
2496        return -(maxScrollZoomY > 0 ? mZoomScrollY * maxScrollZoomY / maxY
2497                : maxScrollZoomY / 2);
2498    }
2499
2500    private void drawMagnifyFrame(Canvas canvas, Rect frame, Paint paint) {
2501        final float ADORNMENT_LEN = 16.0f;
2502        float width = frame.width();
2503        float height = frame.height();
2504        Path path = new Path();
2505        path.moveTo(-ADORNMENT_LEN, -ADORNMENT_LEN);
2506        path.lineTo(0, 0);
2507        path.lineTo(width, 0);
2508        path.lineTo(width + ADORNMENT_LEN, -ADORNMENT_LEN);
2509        path.moveTo(-ADORNMENT_LEN, height + ADORNMENT_LEN);
2510        path.lineTo(0, height);
2511        path.lineTo(width, height);
2512        path.lineTo(width + ADORNMENT_LEN, height + ADORNMENT_LEN);
2513        path.moveTo(0, 0);
2514        path.lineTo(0, height);
2515        path.moveTo(width, 0);
2516        path.lineTo(width, height);
2517        path.offset(frame.left, frame.top);
2518        canvas.drawPath(path, paint);
2519    }
2520
2521    // Returns frame surrounding magified portion of screen while
2522    // scroll-zoom is enabled. The frame is also used to center the
2523    // zoom-in zoom-out points at the start and end of the animation.
2524    private Rect scrollZoomFrame(int width, int height, float halfScale) {
2525        Rect scrollFrame = new Rect();
2526        scrollFrame.set(mZoomScrollX, mZoomScrollY,
2527                mZoomScrollX + width, mZoomScrollY + height);
2528        if (mContentWidth * mZoomScrollLimit < width) {
2529            float scale = zoomFrameScaleX(width, halfScale, 1.0f);
2530            float offsetX = (width * scale - width) * 0.5f;
2531            scrollFrame.left -= offsetX;
2532            scrollFrame.right += offsetX;
2533        }
2534        if (mContentHeight * mZoomScrollLimit < height) {
2535            float scale = zoomFrameScaleY(height, halfScale, 1.0f);
2536            float offsetY = (height * scale - height) * 0.5f;
2537            scrollFrame.top -= offsetY;
2538            scrollFrame.bottom += offsetY;
2539        }
2540        return scrollFrame;
2541    }
2542
2543    private float zoomFrameScaleX(int width, float halfScale, float noScale) {
2544        // mContentWidth > width > mContentWidth * mZoomScrollLimit
2545        if (mContentWidth <= width) {
2546            return halfScale;
2547        }
2548        float part = (width - mContentWidth * mZoomScrollLimit)
2549                / (width * (1 - mZoomScrollLimit));
2550        return halfScale * part + noScale * (1.0f - part);
2551    }
2552
2553    private float zoomFrameScaleY(int height, float halfScale, float noScale) {
2554        if (mContentHeight <= height) {
2555            return halfScale;
2556        }
2557        float part = (height - mContentHeight * mZoomScrollLimit)
2558                / (height * (1 - mZoomScrollLimit));
2559        return halfScale * part + noScale * (1.0f - part);
2560    }
2561
2562    private float scrollZoomMagScale(float invScale) {
2563        return (invScale * 2 + mInvActualScale) / 3;
2564    }
2565
2566    private void scrollZoomDraw(Canvas canvas) {
2567        float invScale = mZoomScrollInvLimit;
2568        int elapsed = 0;
2569        if (mTouchMode != SCROLL_ZOOM_OUT) {
2570            elapsed = (int) Math.min(System.currentTimeMillis()
2571                - mZoomScrollStart, SCROLL_ZOOM_DURATION);
2572            float transitionScale = (mZoomScrollInvLimit - mInvActualScale)
2573                    * elapsed / SCROLL_ZOOM_DURATION;
2574            if (mTouchMode == SCROLL_ZOOM_ANIMATION_OUT) {
2575                invScale = mInvActualScale + transitionScale;
2576            } else { /* if (mTouchMode == SCROLL_ZOOM_ANIMATION_IN) */
2577                invScale = mZoomScrollInvLimit - transitionScale;
2578            }
2579        }
2580        float scale = scrollZoomGridScale(invScale);
2581        invScale = 1.0f / scale;
2582        int width = getViewWidth();
2583        int height = getViewHeight();
2584        float halfScale = scrollZoomMagScale(invScale);
2585        Rect scrollFrame = scrollZoomFrame(width, height, halfScale);
2586        if (elapsed == SCROLL_ZOOM_DURATION) {
2587            if (mTouchMode == SCROLL_ZOOM_ANIMATION_IN) {
2588                setHorizontalScrollBarEnabled(true);
2589                setVerticalScrollBarEnabled(true);
2590                updateTextEntry();
2591                scrollTo((int) (scrollFrame.centerX() * mActualScale)
2592                        - (width >> 1), (int) (scrollFrame.centerY()
2593                        * mActualScale) - (height >> 1));
2594                mTouchMode = TOUCH_DONE_MODE;
2595            } else {
2596                mTouchMode = SCROLL_ZOOM_OUT;
2597            }
2598        }
2599        float newX = scrollZoomX(scale);
2600        float newY = scrollZoomY(scale);
2601        if (LOGV_ENABLED) {
2602            Log.v(LOGTAG, "scrollZoomDraw scale=" + scale + " + (" + newX
2603                    + ", " + newY + ") mZoomScroll=(" + mZoomScrollX + ", "
2604                    + mZoomScrollY + ")" + " invScale=" + invScale + " scale="
2605                    + scale);
2606        }
2607        canvas.translate(newX, newY);
2608        canvas.scale(scale, scale);
2609        boolean animating = mTouchMode != SCROLL_ZOOM_OUT;
2610        if (mDrawHistory) {
2611            int sc = canvas.save(Canvas.CLIP_SAVE_FLAG);
2612            Rect clip = new Rect(0, 0, mHistoryPicture.getWidth(),
2613                    mHistoryPicture.getHeight());
2614            canvas.clipRect(clip, Region.Op.DIFFERENCE);
2615            canvas.drawColor(mBackgroundColor);
2616            canvas.restoreToCount(sc);
2617            canvas.drawPicture(mHistoryPicture);
2618        } else {
2619            mWebViewCore.drawContentPicture(canvas, mBackgroundColor,
2620                    animating, true);
2621        }
2622        if (mTouchMode == TOUCH_DONE_MODE) {
2623            return;
2624        }
2625        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
2626        paint.setStyle(Paint.Style.STROKE);
2627        paint.setStrokeWidth(30.0f);
2628        paint.setARGB(0x50, 0, 0, 0);
2629        int maxX = mContentWidth - width;
2630        int maxY = mContentHeight - height;
2631        if (true) { // experiment: draw hint to place finger off magnify area
2632            drawMagnifyFrame(canvas, scrollFrame, paint);
2633        } else {
2634            canvas.drawRect(scrollFrame, paint);
2635        }
2636        int sc = canvas.save();
2637        canvas.clipRect(scrollFrame);
2638        float halfX = (float) mZoomScrollX / maxX;
2639        if (mContentWidth * mZoomScrollLimit < width) {
2640            halfX = zoomFrameScaleX(width, 0.5f, halfX);
2641        }
2642        float halfY = (float) mZoomScrollY / maxY;
2643        if (mContentHeight * mZoomScrollLimit < height) {
2644            halfY = zoomFrameScaleY(height, 0.5f, halfY);
2645        }
2646        canvas.scale(halfScale, halfScale, mZoomScrollX + width * halfX
2647                , mZoomScrollY + height * halfY);
2648        if (LOGV_ENABLED) {
2649            Log.v(LOGTAG, "scrollZoomDraw halfScale=" + halfScale + " w/h=("
2650                    + width + ", " + height + ") half=(" + halfX + ", "
2651                    + halfY + ")");
2652        }
2653        if (mDrawHistory) {
2654            canvas.drawPicture(mHistoryPicture);
2655        } else {
2656            mWebViewCore.drawContentPicture(canvas, mBackgroundColor,
2657                    animating, false);
2658        }
2659        canvas.restoreToCount(sc);
2660        if (mTouchMode != SCROLL_ZOOM_OUT) {
2661            invalidate();
2662        }
2663    }
2664
2665    private void zoomScrollTap(float x, float y) {
2666        float scale = scrollZoomGridScale(mZoomScrollInvLimit);
2667        float left = scrollZoomX(scale);
2668        float top = scrollZoomY(scale);
2669        int width = getViewWidth();
2670        int height = getViewHeight();
2671        x -= width * scale / 2;
2672        y -= height * scale / 2;
2673        mZoomScrollX = Math.min(mContentWidth - width
2674                , Math.max(0, (int) ((x - left) / scale)));
2675        mZoomScrollY = Math.min(mContentHeight - height
2676                , Math.max(0, (int) ((y - top) / scale)));
2677        if (LOGV_ENABLED) {
2678            Log.v(LOGTAG, "zoomScrollTap scale=" + scale + " + (" + left
2679                    + ", " + top + ") mZoomScroll=(" + mZoomScrollX + ", "
2680                    + mZoomScrollY + ")" + " x=" + x + " y=" + y);
2681        }
2682    }
2683
2684    private boolean canZoomScrollOut() {
2685        if (mContentWidth == 0 || mContentHeight == 0) {
2686            return false;
2687        }
2688        int width = getViewWidth();
2689        int height = getViewHeight();
2690        float x = (float) width / (float) mContentWidth;
2691        float y = (float) height / (float) mContentHeight;
2692        mZoomScrollLimit = Math.max(DEFAULT_MIN_ZOOM_SCALE, Math.min(x, y));
2693        mZoomScrollInvLimit = 1.0f / mZoomScrollLimit;
2694        if (LOGV_ENABLED) {
2695            Log.v(LOGTAG, "canZoomScrollOut"
2696                    + " mInvActualScale=" + mInvActualScale
2697                    + " mZoomScrollLimit=" + mZoomScrollLimit
2698                    + " mZoomScrollInvLimit=" + mZoomScrollInvLimit
2699                    + " mContentWidth=" + mContentWidth
2700                    + " mContentHeight=" + mContentHeight
2701                    );
2702        }
2703        // don't zoom out unless magnify area is at least half as wide
2704        // or tall as content
2705        float limit = mZoomScrollLimit * 2;
2706        return mContentWidth >= width * limit
2707                || mContentHeight >= height * limit;
2708    }
2709
2710    private void startZoomScrollOut() {
2711        setHorizontalScrollBarEnabled(false);
2712        setVerticalScrollBarEnabled(false);
2713        if (getSettings().getBuiltInZoomControls()) {
2714            if (mZoomButtonsController.isVisible()) {
2715                mZoomButtonsController.setVisible(false);
2716            }
2717        } else {
2718            if (mZoomControlRunnable != null) {
2719                mPrivateHandler.removeCallbacks(mZoomControlRunnable);
2720            }
2721            if (mZoomControls != null) {
2722                mZoomControls.hide();
2723            }
2724        }
2725        int width = getViewWidth();
2726        int height = getViewHeight();
2727        int halfW = width >> 1;
2728        mLastTouchX = halfW;
2729        int halfH = height >> 1;
2730        mLastTouchY = halfH;
2731        mScroller.abortAnimation();
2732        mZoomScrollStart = System.currentTimeMillis();
2733        Rect zoomFrame = scrollZoomFrame(width, height
2734                , scrollZoomMagScale(mZoomScrollInvLimit));
2735        mZoomScrollX = Math.max(0, (int) ((mScrollX + halfW) * mInvActualScale)
2736                - (zoomFrame.width() >> 1));
2737        mZoomScrollY = Math.max(0, (int) ((mScrollY + halfH) * mInvActualScale)
2738                - (zoomFrame.height() >> 1));
2739        scrollTo(0, 0); // triggers inval, starts animation
2740        clearTextEntry();
2741        if (LOGV_ENABLED) {
2742            Log.v(LOGTAG, "startZoomScrollOut mZoomScroll=("
2743                    + mZoomScrollX + ", " + mZoomScrollY +")");
2744        }
2745    }
2746
2747    private void zoomScrollOut() {
2748        if (canZoomScrollOut() == false) {
2749            mTouchMode = TOUCH_DONE_MODE;
2750            return;
2751        }
2752        startZoomScrollOut();
2753        mTouchMode = SCROLL_ZOOM_ANIMATION_OUT;
2754        invalidate();
2755    }
2756
2757    private void moveZoomScrollWindow(float x, float y) {
2758        if (Math.abs(x - mLastZoomScrollRawX) < 1.5f
2759                && Math.abs(y - mLastZoomScrollRawY) < 1.5f) {
2760            return;
2761        }
2762        mLastZoomScrollRawX = x;
2763        mLastZoomScrollRawY = y;
2764        int oldX = mZoomScrollX;
2765        int oldY = mZoomScrollY;
2766        int width = getViewWidth();
2767        int height = getViewHeight();
2768        int maxZoomX = mContentWidth - width;
2769        if (maxZoomX > 0) {
2770            int maxScreenX = width - (int) Math.ceil(width
2771                    * mZoomScrollLimit) - SCROLL_ZOOM_FINGER_BUFFER;
2772            if (LOGV_ENABLED) {
2773                Log.v(LOGTAG, "moveZoomScrollWindow-X"
2774                        + " maxScreenX=" + maxScreenX + " width=" + width
2775                        + " mZoomScrollLimit=" + mZoomScrollLimit + " x=" + x);
2776            }
2777            x += maxScreenX * mLastScrollX / maxZoomX - mLastTouchX;
2778            x *= Math.max(maxZoomX / maxScreenX, mZoomScrollInvLimit);
2779            mZoomScrollX = Math.max(0, Math.min(maxZoomX, (int) x));
2780        }
2781        int maxZoomY = mContentHeight - height;
2782        if (maxZoomY > 0) {
2783            int maxScreenY = height - (int) Math.ceil(height
2784                    * mZoomScrollLimit) - SCROLL_ZOOM_FINGER_BUFFER;
2785            if (LOGV_ENABLED) {
2786                Log.v(LOGTAG, "moveZoomScrollWindow-Y"
2787                        + " maxScreenY=" + maxScreenY + " height=" + height
2788                        + " mZoomScrollLimit=" + mZoomScrollLimit + " y=" + y);
2789            }
2790            y += maxScreenY * mLastScrollY / maxZoomY - mLastTouchY;
2791            y *= Math.max(maxZoomY / maxScreenY, mZoomScrollInvLimit);
2792            mZoomScrollY = Math.max(0, Math.min(maxZoomY, (int) y));
2793        }
2794        if (oldX != mZoomScrollX || oldY != mZoomScrollY) {
2795            invalidate();
2796        }
2797        if (LOGV_ENABLED) {
2798            Log.v(LOGTAG, "moveZoomScrollWindow"
2799                    + " scrollTo=(" + mZoomScrollX + ", " + mZoomScrollY + ")"
2800                    + " mLastTouch=(" + mLastTouchX + ", " + mLastTouchY + ")"
2801                    + " maxZoom=(" + maxZoomX + ", " + maxZoomY + ")"
2802                    + " last=("+mLastScrollX+", "+mLastScrollY+")"
2803                    + " x=" + x + " y=" + y);
2804        }
2805    }
2806
2807    private void setZoomScrollIn() {
2808        mZoomScrollStart = System.currentTimeMillis();
2809    }
2810
2811    private float mZoomScrollLimit;
2812    private float mZoomScrollInvLimit;
2813    private int mLastScrollX;
2814    private int mLastScrollY;
2815    private long mZoomScrollStart;
2816    private int mZoomScrollX;
2817    private int mZoomScrollY;
2818    private float mLastZoomScrollRawX = -1000.0f;
2819    private float mLastZoomScrollRawY = -1000.0f;
2820    // The zoomed scale varies from 1.0 to DEFAULT_MIN_ZOOM_SCALE == 0.25.
2821    // The zoom animation duration SCROLL_ZOOM_DURATION == 0.5.
2822    // Two pressures compete for gridding; a high frame rate (e.g. 20 fps)
2823    // and minimizing font cache allocations (fewer frames is better).
2824    // A SCROLL_ZOOM_GRID of 6 permits about 20 zoom levels over 0.5 seconds:
2825    // the inverse of: 1.0, 1.16, 1.33, 1.5, 1.67, 1.84, 2.0, etc. to 4.0
2826    private static final int SCROLL_ZOOM_GRID = 6;
2827    private static final int SCROLL_ZOOM_DURATION = 500;
2828    // Make it easier to get to the bottom of a document by reserving a 32
2829    // pixel buffer, for when the starting drag is a bit below the bottom of
2830    // the magnify frame.
2831    private static final int SCROLL_ZOOM_FINGER_BUFFER = 32;
2832
2833    // draw history
2834    private boolean mDrawHistory = false;
2835    private Picture mHistoryPicture = null;
2836    private int mHistoryWidth = 0;
2837    private int mHistoryHeight = 0;
2838
2839    // Only check the flag, can be called from WebCore thread
2840    boolean drawHistory() {
2841        return mDrawHistory;
2842    }
2843
2844    // Should only be called in UI thread
2845    void switchOutDrawHistory() {
2846        if (null == mWebViewCore) return; // CallbackProxy may trigger this
2847        if (mDrawHistory) {
2848            mDrawHistory = false;
2849            invalidate();
2850            int oldScrollX = mScrollX;
2851            int oldScrollY = mScrollY;
2852            mScrollX = pinLocX(mScrollX);
2853            mScrollY = pinLocY(mScrollY);
2854            if (oldScrollX != mScrollX || oldScrollY != mScrollY) {
2855                mUserScroll = false;
2856                mWebViewCore.sendMessage(EventHub.SYNC_SCROLL, oldScrollX,
2857                        oldScrollY);
2858            }
2859            sendOurVisibleRect();
2860        }
2861    }
2862
2863    /**
2864     *  Class representing the node which is focused.
2865     */
2866    private class FocusNode {
2867        public FocusNode() {
2868            mBounds = new Rect();
2869        }
2870        // Only to be called by JNI
2871        private void setAll(boolean isTextField, boolean isTextArea, boolean
2872                isPassword, boolean isAnchor, boolean isRtlText, int maxLength,
2873                int textSize, int boundsX, int boundsY, int boundsRight, int
2874                boundsBottom, int nodePointer, int framePointer, String text,
2875                String name, int rootTextGeneration) {
2876            mIsTextField        = isTextField;
2877            mIsTextArea         = isTextArea;
2878            mIsPassword         = isPassword;
2879            mIsAnchor           = isAnchor;
2880            mIsRtlText          = isRtlText;
2881
2882            mMaxLength          = maxLength;
2883            mTextSize           = textSize;
2884
2885            mBounds.set(boundsX, boundsY, boundsRight, boundsBottom);
2886
2887
2888            mNodePointer        = nodePointer;
2889            mFramePointer       = framePointer;
2890            mText               = text;
2891            mName               = name;
2892            mRootTextGeneration = rootTextGeneration;
2893        }
2894        public boolean  mIsTextField;
2895        public boolean  mIsTextArea;
2896        public boolean  mIsPassword;
2897        public boolean  mIsAnchor;
2898        public boolean  mIsRtlText;
2899
2900        public int      mSelectionStart;
2901        public int      mSelectionEnd;
2902        public int      mMaxLength;
2903        public int      mTextSize;
2904
2905        public Rect     mBounds;
2906
2907        public int      mNodePointer;
2908        public int      mFramePointer;
2909        public String   mText;
2910        public String   mName;
2911        public int      mRootTextGeneration;
2912    }
2913
2914    // Warning: ONLY use mFocusNode AFTER calling nativeUpdateFocusNode(),
2915    // and ONLY if it returns true;
2916    private FocusNode mFocusNode = new FocusNode();
2917
2918    /**
2919     *  Delete text from start to end in the focused textfield. If there is no
2920     *  focus, or if start == end, silently fail.  If start and end are out of
2921     *  order, swap them.
2922     *  @param  start   Beginning of selection to delete.
2923     *  @param  end     End of selection to delete.
2924     */
2925    /* package */ void deleteSelection(int start, int end) {
2926        mTextGeneration++;
2927        mWebViewCore.sendMessage(EventHub.DELETE_SELECTION, start, end,
2928                new WebViewCore.FocusData(mFocusData));
2929    }
2930
2931    /**
2932     *  Set the selection to (start, end) in the focused textfield. If start and
2933     *  end are out of order, swap them.
2934     *  @param  start   Beginning of selection.
2935     *  @param  end     End of selection.
2936     */
2937    /* package */ void setSelection(int start, int end) {
2938        mWebViewCore.sendMessage(EventHub.SET_SELECTION, start, end,
2939                new WebViewCore.FocusData(mFocusData));
2940    }
2941
2942    // Called by JNI when a touch event puts a textfield into focus.
2943    private void displaySoftKeyboard() {
2944        InputMethodManager imm = (InputMethodManager)
2945                getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
2946        imm.showSoftInput(mTextEntry, 0);
2947        mTextEntry.enableScrollOnScreen(true);
2948        // Now we need to fake a touch event to place the cursor where the
2949        // user touched.
2950        AbsoluteLayout.LayoutParams lp = (AbsoluteLayout.LayoutParams)
2951                mTextEntry.getLayoutParams();
2952        if (lp != null) {
2953            // Take the last touch and adjust for the location of the
2954            // TextDialog.
2955            float x = mLastTouchX + (float) (mScrollX - lp.x);
2956            float y = mLastTouchY + (float) (mScrollY - lp.y);
2957            mTextEntry.fakeTouchEvent(x, y);
2958        }
2959    }
2960
2961    private void updateTextEntry() {
2962        if (mTextEntry == null) {
2963            mTextEntry = new TextDialog(mContext, WebView.this);
2964            // Initialize our generation number.
2965            mTextGeneration = 0;
2966        }
2967        // If we do not have focus, do nothing until we gain focus.
2968        if (!hasFocus() && !mTextEntry.hasFocus()
2969                || (mTouchMode >= FIRST_SCROLL_ZOOM
2970                && mTouchMode <= LAST_SCROLL_ZOOM)) {
2971            mNeedsUpdateTextEntry = true;
2972            return;
2973        }
2974        boolean alreadyThere = inEditingMode();
2975        if (0 == mNativeClass || !nativeUpdateFocusNode()) {
2976            if (alreadyThere) {
2977                mTextEntry.remove();
2978            }
2979            return;
2980        }
2981        FocusNode node = mFocusNode;
2982        if (!node.mIsTextField && !node.mIsTextArea) {
2983            if (alreadyThere) {
2984                mTextEntry.remove();
2985            }
2986            return;
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, true);
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, true)) {
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                return true;
4933            }
4934
4935            private Container item(int position) {
4936                if (position < 0 || position >= getCount()) {
4937                    return null;
4938                }
4939                return (Container) getItem(position);
4940            }
4941
4942            @Override
4943            public long getItemId(int position) {
4944                Container item = item(position);
4945                if (item == null) {
4946                    return -1;
4947                }
4948                return item.mId;
4949            }
4950
4951            @Override
4952            public boolean areAllItemsEnabled() {
4953                return false;
4954            }
4955
4956            @Override
4957            public boolean isEnabled(int position) {
4958                Container item = item(position);
4959                if (item == null) {
4960                    return false;
4961                }
4962                return item.mEnabled;
4963            }
4964        }
4965
4966        private InvokeListBox(String[] array,
4967                boolean[] enabled, int[] selected) {
4968            mMultiple = true;
4969            mSelectedArray = selected;
4970
4971            int length = array.length;
4972            mContainers = new Container[length];
4973            for (int i = 0; i < length; i++) {
4974                mContainers[i] = new Container();
4975                mContainers[i].mString = array[i];
4976                mContainers[i].mEnabled = enabled[i];
4977                mContainers[i].mId = i;
4978            }
4979        }
4980
4981        private InvokeListBox(String[] array, boolean[] enabled, int
4982                selection) {
4983            mSelection = selection;
4984            mMultiple = false;
4985
4986            int length = array.length;
4987            mContainers = new Container[length];
4988            for (int i = 0; i < length; i++) {
4989                mContainers[i] = new Container();
4990                mContainers[i].mString = array[i];
4991                mContainers[i].mEnabled = enabled[i];
4992                mContainers[i].mId = i;
4993            }
4994        }
4995
4996        public void run() {
4997            final ListView listView = (ListView) LayoutInflater.from(mContext)
4998                    .inflate(com.android.internal.R.layout.select_dialog, null);
4999            final MyArrayListAdapter adapter = new
5000                    MyArrayListAdapter(mContext, mContainers, mMultiple);
5001            AlertDialog.Builder b = new AlertDialog.Builder(mContext)
5002                    .setView(listView).setCancelable(true)
5003                    .setInverseBackgroundForced(true);
5004
5005            if (mMultiple) {
5006                b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
5007                    public void onClick(DialogInterface dialog, int which) {
5008                        mWebViewCore.sendMessage(
5009                                EventHub.LISTBOX_CHOICES,
5010                                adapter.getCount(), 0,
5011                                listView.getCheckedItemPositions());
5012                    }});
5013                b.setNegativeButton(android.R.string.cancel,
5014                        new DialogInterface.OnClickListener() {
5015                    public void onClick(DialogInterface dialog, int which) {
5016                        mWebViewCore.sendMessage(
5017                                EventHub.SINGLE_LISTBOX_CHOICE, -2, 0);
5018                }});
5019            }
5020            final AlertDialog dialog = b.create();
5021            listView.setAdapter(adapter);
5022            listView.setFocusableInTouchMode(true);
5023            // There is a bug (1250103) where the checks in a ListView with
5024            // multiple items selected are associated with the positions, not
5025            // the ids, so the items do not properly retain their checks when
5026            // filtered.  Do not allow filtering on multiple lists until
5027            // that bug is fixed.
5028
5029            // Disable filter altogether
5030            // listView.setTextFilterEnabled(!mMultiple);
5031            if (mMultiple) {
5032                listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
5033                int length = mSelectedArray.length;
5034                for (int i = 0; i < length; i++) {
5035                    listView.setItemChecked(mSelectedArray[i], true);
5036                }
5037            } else {
5038                listView.setOnItemClickListener(new OnItemClickListener() {
5039                    public void onItemClick(AdapterView parent, View v,
5040                            int position, long id) {
5041                        mWebViewCore.sendMessage(
5042                                EventHub.SINGLE_LISTBOX_CHOICE, (int)id, 0);
5043                        dialog.dismiss();
5044                    }
5045                });
5046                if (mSelection != -1) {
5047                    listView.setSelection(mSelection);
5048                    listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
5049                    listView.setItemChecked(mSelection, true);
5050                }
5051            }
5052            dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
5053                public void onCancel(DialogInterface dialog) {
5054                    mWebViewCore.sendMessage(
5055                                EventHub.SINGLE_LISTBOX_CHOICE, -2, 0);
5056                }
5057            });
5058            dialog.show();
5059        }
5060    }
5061
5062    /*
5063     * Request a dropdown menu for a listbox with multiple selection.
5064     *
5065     * @param array Labels for the listbox.
5066     * @param enabledArray  Which positions are enabled.
5067     * @param selectedArray Which positions are initally selected.
5068     */
5069    void requestListBox(String[] array, boolean[]enabledArray, int[]
5070            selectedArray) {
5071        mPrivateHandler.post(
5072                new InvokeListBox(array, enabledArray, selectedArray));
5073    }
5074
5075    /*
5076     * Request a dropdown menu for a listbox with single selection or a single
5077     * <select> element.
5078     *
5079     * @param array Labels for the listbox.
5080     * @param enabledArray  Which positions are enabled.
5081     * @param selection Which position is initally selected.
5082     */
5083    void requestListBox(String[] array, boolean[]enabledArray, int selection) {
5084        mPrivateHandler.post(
5085                new InvokeListBox(array, enabledArray, selection));
5086    }
5087
5088    // called by JNI
5089    private void sendFinalFocus(int frame, int node, int x, int y) {
5090        WebViewCore.FocusData focusData = new WebViewCore.FocusData();
5091        focusData.mFrame = frame;
5092        focusData.mNode = node;
5093        focusData.mX = x;
5094        focusData.mY = y;
5095        mWebViewCore.sendMessage(EventHub.SET_FINAL_FOCUS,
5096                EventHub.NO_FOCUS_CHANGE_BLOCK, 0, focusData);
5097    }
5098
5099    // called by JNI
5100    private void setFocusData(int moveGeneration, int buildGeneration,
5101            int frame, int node, int x, int y, boolean ignoreNullFocus) {
5102        mFocusData.mMoveGeneration = moveGeneration;
5103        mFocusData.mBuildGeneration = buildGeneration;
5104        mFocusData.mFrame = frame;
5105        mFocusData.mNode = node;
5106        mFocusData.mX = x;
5107        mFocusData.mY = y;
5108        mFocusData.mIgnoreNullFocus = ignoreNullFocus;
5109    }
5110
5111    // called by JNI
5112    private void sendKitFocus() {
5113        WebViewCore.FocusData focusData = new WebViewCore.FocusData(mFocusData);
5114        mWebViewCore.sendMessage(EventHub.SET_KIT_FOCUS, focusData);
5115    }
5116
5117    // called by JNI
5118    private void sendMotionUp(int touchGeneration, int buildGeneration,
5119            int frame, int node, int x, int y, int size, boolean isClick,
5120            boolean retry) {
5121        WebViewCore.TouchUpData touchUpData = new WebViewCore.TouchUpData();
5122        touchUpData.mMoveGeneration = touchGeneration;
5123        touchUpData.mBuildGeneration = buildGeneration;
5124        touchUpData.mSize = size;
5125        touchUpData.mIsClick = isClick;
5126        touchUpData.mRetry = retry;
5127        mFocusData.mFrame = touchUpData.mFrame = frame;
5128        mFocusData.mNode = touchUpData.mNode = node;
5129        mFocusData.mX = touchUpData.mX = x;
5130        mFocusData.mY = touchUpData.mY = y;
5131        mWebViewCore.sendMessage(EventHub.TOUCH_UP, touchUpData);
5132    }
5133
5134
5135    private int getScaledMaxXScroll() {
5136        int width;
5137        if (mHeightCanMeasure == false) {
5138            width = getViewWidth() / 4;
5139        } else {
5140            Rect visRect = new Rect();
5141            calcOurVisibleRect(visRect);
5142            width = visRect.width() / 2;
5143        }
5144        // FIXME the divisor should be retrieved from somewhere
5145        return viewToContent(width);
5146    }
5147
5148    private int getScaledMaxYScroll() {
5149        int height;
5150        if (mHeightCanMeasure == false) {
5151            height = getViewHeight() / 4;
5152        } else {
5153            Rect visRect = new Rect();
5154            calcOurVisibleRect(visRect);
5155            height = visRect.height() / 2;
5156        }
5157        // FIXME the divisor should be retrieved from somewhere
5158        // the closest thing today is hard-coded into ScrollView.java
5159        // (from ScrollView.java, line 363)   int maxJump = height/2;
5160        return viewToContent(height);
5161    }
5162
5163    /**
5164     * Called by JNI to invalidate view
5165     */
5166    private void viewInvalidate() {
5167        invalidate();
5168    }
5169
5170    // return true if the key was handled
5171    private boolean navHandledKey(int keyCode, int count, boolean noScroll
5172            , long time) {
5173        if (mNativeClass == 0) {
5174            return false;
5175        }
5176        mLastFocusTime = time;
5177        mLastFocusBounds = nativeGetFocusRingBounds();
5178        boolean keyHandled = nativeMoveFocus(keyCode, count, noScroll) == false;
5179        if (LOGV_ENABLED) {
5180            Log.v(LOGTAG, "navHandledKey mLastFocusBounds=" + mLastFocusBounds
5181                    + " mLastFocusTime=" + mLastFocusTime
5182                    + " handled=" + keyHandled);
5183        }
5184        if (keyHandled == false || mHeightCanMeasure == false) {
5185            return keyHandled;
5186        }
5187        Rect contentFocus = nativeGetFocusRingBounds();
5188        if (contentFocus.isEmpty()) return keyHandled;
5189        Rect viewFocus = contentToView(contentFocus);
5190        Rect visRect = new Rect();
5191        calcOurVisibleRect(visRect);
5192        Rect outset = new Rect(visRect);
5193        int maxXScroll = visRect.width() / 2;
5194        int maxYScroll = visRect.height() / 2;
5195        outset.inset(-maxXScroll, -maxYScroll);
5196        if (Rect.intersects(outset, viewFocus) == false) {
5197            return keyHandled;
5198        }
5199        // FIXME: Necessary because ScrollView/ListView do not scroll left/right
5200        int maxH = Math.min(viewFocus.right - visRect.right, maxXScroll);
5201        if (maxH > 0) {
5202            pinScrollBy(maxH, 0, true, 0);
5203        } else {
5204            maxH = Math.max(viewFocus.left - visRect.left, -maxXScroll);
5205            if (maxH < 0) {
5206                pinScrollBy(maxH, 0, true, 0);
5207            }
5208        }
5209        if (mLastFocusBounds.isEmpty()) return keyHandled;
5210        if (mLastFocusBounds.equals(contentFocus)) return keyHandled;
5211        if (LOGV_ENABLED) {
5212            Log.v(LOGTAG, "navHandledKey contentFocus=" + contentFocus);
5213        }
5214        requestRectangleOnScreen(viewFocus);
5215        mUserScroll = true;
5216        return keyHandled;
5217    }
5218
5219    /**
5220     * Set the background color. It's white by default. Pass
5221     * zero to make the view transparent.
5222     * @param color   the ARGB color described by Color.java
5223     */
5224    public void setBackgroundColor(int color) {
5225        mBackgroundColor = color;
5226        mWebViewCore.sendMessage(EventHub.SET_BACKGROUND_COLOR, color);
5227    }
5228
5229    public void debugDump() {
5230        nativeDebugDump();
5231        mWebViewCore.sendMessage(EventHub.DUMP_NAVTREE);
5232    }
5233
5234    /**
5235     *  Update our cache with updatedText.
5236     *  @param updatedText  The new text to put in our cache.
5237     */
5238    /* package */ void updateCachedTextfield(String updatedText) {
5239        // Also place our generation number so that when we look at the cache
5240        // we recognize that it is up to date.
5241        nativeUpdateCachedTextfield(updatedText, mTextGeneration);
5242    }
5243
5244    // Never call this version except by updateCachedTextfield(String) -
5245    // we always want to pass in our generation number.
5246    private native void     nativeUpdateCachedTextfield(String updatedText,
5247            int generation);
5248    private native void     nativeClearFocus(int x, int y);
5249    private native void     nativeCreate(int ptr);
5250    private native void     nativeDebugDump();
5251    private native void     nativeDestroy();
5252    private native void     nativeDrawFocusRing(Canvas content);
5253    private native void     nativeDrawSelection(Canvas content
5254            , int x, int y, boolean extendSelection);
5255    private native void     nativeDrawSelectionRegion(Canvas content);
5256    private native boolean  nativeUpdateFocusNode();
5257    private native Rect     nativeGetFocusRingBounds();
5258    private native Rect     nativeGetNavBounds();
5259    private native void     nativeInstrumentReport();
5260    private native void     nativeMarkNodeInvalid(int node);
5261    // return true if the page has been scrolled
5262    private native boolean  nativeMotionUp(int x, int y, int slop, boolean isClick);
5263    // returns false if it handled the key
5264    private native boolean  nativeMoveFocus(int keyCode, int count,
5265            boolean noScroll);
5266    private native void     nativeNotifyFocusSet(boolean inEditingMode);
5267    private native void     nativeRecomputeFocus();
5268    // Like many other of our native methods, you must make sure that
5269    // mNativeClass is not null before calling this method.
5270    private native void     nativeRecordButtons(boolean focused,
5271            boolean pressed, boolean invalidate);
5272    private native void     nativeResetFocus();
5273    private native void     nativeResetNavClipBounds();
5274    private native void     nativeSelectBestAt(Rect rect);
5275    private native void     nativeSetFindIsDown();
5276    private native void     nativeSetFollowedLink(boolean followed);
5277    private native void     nativeSetHeightCanMeasure(boolean measure);
5278    private native void     nativeSetNavBounds(Rect rect);
5279    private native void     nativeSetNavClipBounds(Rect rect);
5280    private native String   nativeImageURI(int x, int y);
5281    /**
5282     * Returns true if the native focus nodes says it wants to handle key events
5283     * (ala plugins). This can only be called if mNativeClass is non-zero!
5284     */
5285    private native boolean  nativeFocusNodeWantsKeyEvents();
5286    private native void     nativeMoveSelection(int x, int y
5287            , boolean extendSelection);
5288    private native Region   nativeGetSelection();
5289
5290    private native void nativeDumpDisplayTree(String urlOrNull);
5291}
5292