diff options
Diffstat (limited to 'core/java/android/webkit/WebView.java')
-rw-r--r-- | core/java/android/webkit/WebView.java | 3192 |
1 files changed, 1745 insertions, 1447 deletions
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 2892051..142dffb 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -25,12 +25,11 @@ import android.database.DataSetObserver; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Path; import android.graphics.Picture; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; +import android.graphics.drawable.Drawable; import android.net.http.SslCertificate; import android.net.Uri; import android.os.Bundle; @@ -45,6 +44,7 @@ import android.text.Spannable; import android.util.AttributeSet; import android.util.EventLog; import android.util.Log; +import android.util.TypedValue; import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -58,14 +58,13 @@ import android.view.ViewParent; import android.view.ViewTreeObserver; import android.view.animation.AlphaAnimation; import android.view.inputmethod.InputMethodManager; -import android.webkit.TextDialog.AutoCompleteAdapter; +import android.webkit.WebTextView.AutoCompleteAdapter; import android.webkit.WebViewCore.EventHub; import android.widget.AbsoluteLayout; import android.widget.Adapter; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.FrameLayout; -import android.widget.ImageView; import android.widget.ListView; import android.widget.Scroller; import android.widget.Toast; @@ -80,11 +79,11 @@ import java.io.FileOutputStream; import java.io.IOException; import java.net.URLDecoder; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; +import java.util.Map; /** - * <p>A View that displays web pages. This class is the basis upon which you + * <p>A View that displays web pages. This class is the basis upon which you * can roll your own web browser or simply display some online content within your Activity. * It uses the WebKit rendering engine to display * web pages and includes methods to navigate forward and backward @@ -93,7 +92,7 @@ import java.util.List; * {@link #getSettings() WebSettings}.{@link WebSettings#setBuiltInZoomControls(boolean)} * (introduced in API version 3). * <p>Note that, in order for your Activity to access the Internet and load web pages - * in a WebView, you must add the <var>INTERNET</var> permissions to your + * in a WebView, you must add the <var>INTERNET</var> permissions to your * Android Manifest file:</p> * <pre><uses-permission android:name="android.permission.INTERNET" /></pre> * @@ -195,7 +194,7 @@ import java.util.List; * changes, and then just leave the WebView alone. It'll automatically * re-orient itself as appropriate.</p> */ -public class WebView extends AbsoluteLayout +public class WebView extends AbsoluteLayout implements ViewTreeObserver.OnGlobalFocusChangeListener, ViewGroup.OnHierarchyChangeListener { @@ -205,62 +204,52 @@ public class WebView extends AbsoluteLayout // true means redraw the screen all-the-time. Only with AUTO_REDRAW_HACK private boolean mAutoRedraw; - // keep debugging parameters near the top of the file static final String LOGTAG = "webview"; - static final boolean DEBUG = false; - static final boolean LOGV_ENABLED = DEBUG; - private class ExtendedZoomControls extends FrameLayout { + private static class ExtendedZoomControls extends FrameLayout { public ExtendedZoomControls(Context context, AttributeSet attrs) { super(context, attrs); LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); inflater.inflate(com.android.internal.R.layout.zoom_magnify, this, true); - mZoomControls = (ZoomControls) findViewById(com.android.internal.R.id.zoomControls); - mZoomMagnify = (ImageView) findViewById(com.android.internal.R.id.zoomMagnify); + mPlusMinusZoomControls = (ZoomControls) findViewById( + com.android.internal.R.id.zoomControls); + findViewById(com.android.internal.R.id.zoomMagnify).setVisibility( + View.GONE); } - + public void show(boolean showZoom, boolean canZoomOut) { - mZoomControls.setVisibility(showZoom ? View.VISIBLE : View.GONE); - mZoomMagnify.setVisibility(canZoomOut ? View.VISIBLE : View.GONE); + mPlusMinusZoomControls.setVisibility( + showZoom ? View.VISIBLE : View.GONE); fade(View.VISIBLE, 0.0f, 1.0f); } - + public void hide() { fade(View.GONE, 1.0f, 0.0f); } - + private void fade(int visibility, float startAlpha, float endAlpha) { AlphaAnimation anim = new AlphaAnimation(startAlpha, endAlpha); anim.setDuration(500); startAnimation(anim); setVisibility(visibility); } - - public void setIsZoomMagnifyEnabled(boolean isEnabled) { - mZoomMagnify.setEnabled(isEnabled); - } - + public boolean hasFocus() { - return mZoomControls.hasFocus() || mZoomMagnify.hasFocus(); + return mPlusMinusZoomControls.hasFocus(); } - + public void setOnZoomInClickListener(OnClickListener listener) { - mZoomControls.setOnZoomInClickListener(listener); + mPlusMinusZoomControls.setOnZoomInClickListener(listener); } - + public void setOnZoomOutClickListener(OnClickListener listener) { - mZoomControls.setOnZoomOutClickListener(listener); - } - - public void setOnZoomMagnifyClickListener(OnClickListener listener) { - mZoomMagnify.setOnClickListener(listener); + mPlusMinusZoomControls.setOnZoomOutClickListener(listener); } - ZoomControls mZoomControls; - ImageView mZoomMagnify; + ZoomControls mPlusMinusZoomControls; } - + /** * Transportation object for returning WebView across thread boundaries. */ @@ -300,13 +289,13 @@ public class WebView extends AbsoluteLayout private WebViewCore mWebViewCore; // Handler for dispatching UI messages. /* package */ final Handler mPrivateHandler = new PrivateHandler(); - private TextDialog mTextEntry; + private WebTextView mWebTextView; // Used to ignore changes to webkit text that arrives to the UI side after // more key events. private int mTextGeneration; - // The list of loaded plugins. - private static PluginList sPluginList; + // Used by WebViewCore to create child views. + /* package */ final ViewManager mViewManager; /** * Position of the last touch event. @@ -326,15 +315,27 @@ public class WebView extends AbsoluteLayout /** * The minimum elapsed time before sending another ACTION_MOVE event to - * WebViewCore + * WebViewCore. This really should be tuned for each type of the devices. + * For example in Google Map api test case, it takes Dream device at least + * 150ms to do a full cycle in the WebViewCore by processing a touch event, + * triggering the layout and drawing the picture. While the same process + * takes 60+ms on the current high speed device. If we make + * TOUCH_SENT_INTERVAL too small, there will be multiple touch events sent + * to WebViewCore queue and the real layout and draw events will be pushed + * to further, which slows down the refresh rate. Choose 50 to favor the + * current high speed devices. For Dream like devices, 100 is a better + * choice. Maybe make this in the buildspec later. */ - private static final int TOUCH_SENT_INTERVAL = 100; + private static final int TOUCH_SENT_INTERVAL = 50; /** * Helper class to get velocity for fling */ VelocityTracker mVelocityTracker; private int mMaximumFling; + private float mLastVelocity; + private float mLastVelX; + private float mLastVelY; /** * Touch mode @@ -345,16 +346,9 @@ public class WebView extends AbsoluteLayout private static final int TOUCH_DRAG_MODE = 3; private static final int TOUCH_SHORTPRESS_START_MODE = 4; private static final int TOUCH_SHORTPRESS_MODE = 5; - private static final int TOUCH_DOUBLECLICK_MODE = 6; + private static final int TOUCH_DOUBLE_TAP_MODE = 6; private static final int TOUCH_DONE_MODE = 7; private static final int TOUCH_SELECT_MODE = 8; - // touch mode values specific to scale+scroll - private static final int FIRST_SCROLL_ZOOM = 9; - private static final int SCROLL_ZOOM_ANIMATION_IN = 9; - private static final int SCROLL_ZOOM_ANIMATION_OUT = 10; - private static final int SCROLL_ZOOM_OUT = 11; - private static final int LAST_SCROLL_ZOOM = 11; - // end of touch mode values specific to scale+scroll // Whether to forward the touch events to WebCore private boolean mForwardTouchEvents = false; @@ -362,20 +356,28 @@ public class WebView extends AbsoluteLayout // Whether to prevent drag during touch. The initial value depends on // mForwardTouchEvents. If WebCore wants touch events, we assume it will // take control of touch events unless it says no for touch down event. - private boolean mPreventDrag; + private static final int PREVENT_DRAG_NO = 0; + private static final int PREVENT_DRAG_MAYBE_YES = 1; + private static final int PREVENT_DRAG_YES = 2; + private int mPreventDrag = PREVENT_DRAG_NO; + + // To keep track of whether the current drag was initiated by a WebTextView, + // so that we know not to hide the cursor + boolean mDragFromTextInput; - // If updateTextEntry gets called while we are out of focus, use this - // variable to remember to do it next time we gain focus. - private boolean mNeedsUpdateTextEntry = false; - - // Whether or not to draw the focus ring. - private boolean mDrawFocusRing = true; + // Whether or not to draw the cursor ring. + private boolean mDrawCursorRing = true; + + // true if onPause has been called (and not onResume) + private boolean mIsPaused; /** * Customizable constant */ // pre-computed square of ViewConfiguration.getScaledTouchSlop() private int mTouchSlopSquare; + // pre-computed square of ViewConfiguration.getScaledDoubleTapSlop() + private int mDoubleTapSlopSquare; // pre-computed density adjusted navigation slop private int mNavSlop; // This should be ViewConfiguration.getTapTimeout() @@ -389,7 +391,7 @@ public class WebView extends AbsoluteLayout // needed to avoid flinging after a pause of no movement private static final int MIN_FLING_TIME = 250; // The time that the Zoom Controls are visible before fading away - private static final long ZOOM_CONTROLS_TIMEOUT = + private static final long ZOOM_CONTROLS_TIMEOUT = ViewConfiguration.getZoomControlsTimeout(); // The amount of content to overlap between two screens when going through // pages with the space bar, in pixels. @@ -410,7 +412,7 @@ public class WebView extends AbsoluteLayout private int mContentWidth; // cache of value from WebViewCore private int mContentHeight; // cache of value from WebViewCore - // Need to have the separate control for horizontal and vertical scrollbar + // Need to have the separate control for horizontal and vertical scrollbar // style than the View's single scrollbar style private boolean mOverlayHorizontalScrollbar = true; private boolean mOverlayVerticalScrollbar = false; @@ -421,55 +423,53 @@ public class WebView extends AbsoluteLayout private static final int STD_SPEED = 480; // pixels per second // time for the longest scroll animation private static final int MAX_DURATION = 750; // milliseconds + private static final int SLIDE_TITLE_DURATION = 500; // milliseconds private Scroller mScroller; private boolean mWrapContent; - // true if we should call webcore to draw the content, false means we have - // requested something but it isn't ready to draw yet. - private WebViewCore.FocusData mFocusData; /** * Private message ids */ - private static final int REMEMBER_PASSWORD = 1; - private static final int NEVER_REMEMBER_PASSWORD = 2; - private static final int SWITCH_TO_SHORTPRESS = 3; - private static final int SWITCH_TO_LONGPRESS = 4; - private static final int UPDATE_TEXT_ENTRY_ADAPTER = 6; - private static final int SWITCH_TO_ENTER = 7; - private static final int RESUME_WEBCORE_UPDATE = 8; + private static final int REMEMBER_PASSWORD = 1; + private static final int NEVER_REMEMBER_PASSWORD = 2; + private static final int SWITCH_TO_SHORTPRESS = 3; + private static final int SWITCH_TO_LONGPRESS = 4; + private static final int RELEASE_SINGLE_TAP = 5; + private static final int REQUEST_FORM_DATA = 6; + private static final int RESUME_WEBCORE_UPDATE = 7; //! arg1=x, arg2=y - static final int SCROLL_TO_MSG_ID = 10; - static final int SCROLL_BY_MSG_ID = 11; + static final int SCROLL_TO_MSG_ID = 10; + static final int SCROLL_BY_MSG_ID = 11; //! arg1=x, arg2=y - static final int SPAWN_SCROLL_TO_MSG_ID = 12; + static final int SPAWN_SCROLL_TO_MSG_ID = 12; //! arg1=x, arg2=y - static final int SYNC_SCROLL_TO_MSG_ID = 13; - static final int NEW_PICTURE_MSG_ID = 14; - static final int UPDATE_TEXT_ENTRY_MSG_ID = 15; - static final int WEBCORE_INITIALIZED_MSG_ID = 16; - static final int UPDATE_TEXTFIELD_TEXT_MSG_ID = 17; - static final int DID_FIRST_LAYOUT_MSG_ID = 18; - static final int RECOMPUTE_FOCUS_MSG_ID = 19; - static final int NOTIFY_FOCUS_SET_MSG_ID = 20; - static final int MARK_NODE_INVALID_ID = 21; - static final int UPDATE_CLIPBOARD = 22; - static final int LONG_PRESS_ENTER = 23; - static final int PREVENT_TOUCH_ID = 24; - static final int WEBCORE_NEED_TOUCH_EVENTS = 25; + static final int SYNC_SCROLL_TO_MSG_ID = 13; + static final int NEW_PICTURE_MSG_ID = 14; + static final int UPDATE_TEXT_ENTRY_MSG_ID = 15; + static final int WEBCORE_INITIALIZED_MSG_ID = 16; + static final int UPDATE_TEXTFIELD_TEXT_MSG_ID = 17; + static final int MOVE_OUT_OF_PLUGIN = 19; + static final int CLEAR_TEXT_ENTRY = 20; + static final int UPDATE_TEXT_SELECTION_MSG_ID = 21; + static final int UPDATE_CLIPBOARD = 22; + static final int LONG_PRESS_CENTER = 23; + static final int PREVENT_TOUCH_ID = 24; + static final int WEBCORE_NEED_TOUCH_EVENTS = 25; // obj=Rect in doc coordinates - static final int INVAL_RECT_MSG_ID = 26; - + static final int INVAL_RECT_MSG_ID = 26; + static final int REQUEST_KEYBOARD = 27; + static final String[] HandlerDebugString = { - "REMEMBER_PASSWORD", // = 1; - "NEVER_REMEMBER_PASSWORD", // = 2; - "SWITCH_TO_SHORTPRESS", // = 3; - "SWITCH_TO_LONGPRESS", // = 4; - "5", - "UPDATE_TEXT_ENTRY_ADAPTER", // = 6; - "SWITCH_TO_ENTER", // = 7; - "RESUME_WEBCORE_UPDATE", // = 8; + "REMEMBER_PASSWORD", // = 1; + "NEVER_REMEMBER_PASSWORD", // = 2; + "SWITCH_TO_SHORTPRESS", // = 3; + "SWITCH_TO_LONGPRESS", // = 4; + "RELEASE_SINGLE_TAP", // = 5; + "REQUEST_FORM_DATA", // = 6; + "SWITCH_TO_CLICK", // = 7; + "RESUME_WEBCORE_UPDATE", // = 8; "9", "SCROLL_TO_MSG_ID", // = 10; "SCROLL_BY_MSG_ID", // = 11; @@ -479,31 +479,40 @@ public class WebView extends AbsoluteLayout "UPDATE_TEXT_ENTRY_MSG_ID", // = 15; "WEBCORE_INITIALIZED_MSG_ID", // = 16; "UPDATE_TEXTFIELD_TEXT_MSG_ID", // = 17; - "DID_FIRST_LAYOUT_MSG_ID", // = 18; - "RECOMPUTE_FOCUS_MSG_ID", // = 19; - "NOTIFY_FOCUS_SET_MSG_ID", // = 20; - "MARK_NODE_INVALID_ID", // = 21; + "18", // = 18; + "MOVE_OUT_OF_PLUGIN", // = 19; + "CLEAR_TEXT_ENTRY", // = 20; + "UPDATE_TEXT_SELECTION_MSG_ID", // = 21; "UPDATE_CLIPBOARD", // = 22; - "LONG_PRESS_ENTER", // = 23; + "LONG_PRESS_CENTER", // = 23; "PREVENT_TOUCH_ID", // = 24; "WEBCORE_NEED_TOUCH_EVENTS", // = 25; - "INVAL_RECT_MSG_ID" // = 26; + "INVAL_RECT_MSG_ID", // = 26; + "REQUEST_KEYBOARD" // = 27; }; - // width which view is considered to be fully zoomed out - static final int ZOOM_OUT_WIDTH = 1008; - // default scale limit. Depending on the display density private static float DEFAULT_MAX_ZOOM_SCALE; private static float DEFAULT_MIN_ZOOM_SCALE; // scale limit, which can be set through viewport meta tag in the web page private float mMaxZoomScale; private float mMinZoomScale; - private boolean mMinZoomScaleFixed = false; + private boolean mMinZoomScaleFixed = true; // initial scale in percent. 0 means using default. private int mInitialScale = 0; + // while in the zoom overview mode, the page's width is fully fit to the + // current window. The page is alive, in another words, you can click to + // follow the links. Double tap will toggle between zoom overview mode and + // the last zoom scale. + boolean mInZoomOverview = false; + + // ideally mZoomOverviewWidth should be mContentWidth. But sites like espn, + // engadget always have wider mContentWidth no matter what viewport size is. + int mZoomOverviewWidth = WebViewCore.DEFAULT_VIEWPORT_WIDTH; + float mLastScale; + // default scale. Depending on the display density. static int DEFAULT_SCALE_PERCENT; private float mDefaultScale; @@ -518,6 +527,8 @@ public class WebView extends AbsoluteLayout private float mZoomScale; private float mInvInitialZoomScale; private float mInvFinalZoomScale; + private int mInitialScrollX; + private int mInitialScrollY; private long mZoomStart; private static final int ZOOM_ANIMATION_LENGTH = 500; @@ -530,7 +541,7 @@ public class WebView extends AbsoluteLayout private static final int SNAP_X_LOCK = 4; private static final int SNAP_Y_LOCK = 5; private boolean mSnapPositive; - + // Used to match key downs and key ups private boolean mGotKeyDown; @@ -553,7 +564,7 @@ public class WebView extends AbsoluteLayout * URI scheme for map address */ public static final String SCHEME_GEO = "geo:0,0?q="; - + private int mBackgroundColor = Color.WHITE; // Used to notify listeners of a new picture. @@ -570,7 +581,8 @@ public class WebView extends AbsoluteLayout public void onNewPicture(WebView view, Picture picture); } - public class HitTestResult { + // FIXME: Want to make this public, but need to change the API file. + public /*static*/ class HitTestResult { /** * Default HitTestResult, where the target is unknown */ @@ -640,8 +652,7 @@ public class WebView extends AbsoluteLayout private ExtendedZoomControls mZoomControls; private Runnable mZoomControlRunnable; - private ZoomButtonsController mZoomButtonsController; - private ImageView mZoomOverviewButton; + private ZoomButtonsController mZoomButtonsController; // These keep track of the center point of the zoom. They are used to // determine the point around which we should zoom. @@ -664,11 +675,11 @@ public class WebView extends AbsoluteLayout } else { zoomOut(); } - + updateZoomButtonsEnabled(); } }; - + /** * Construct a new WebView with a Context object. * @param context A Context object used to access application assets. @@ -693,24 +704,33 @@ public class WebView extends AbsoluteLayout * @param defStyle The default style resource ID. */ public WebView(Context context, AttributeSet attrs, int defStyle) { + this(context, attrs, defStyle, null); + } + + /** + * Construct a new WebView with layout parameters, a default style and a set + * of custom Javscript interfaces to be added to the WebView at initialization + * time. This guraratees that these interfaces will be available when the JS + * context is initialized. + * @param context A Context object used to access application assets. + * @param attrs An AttributeSet passed to our parent. + * @param defStyle The default style resource ID. + * @param javascriptInterfaces is a Map of intareface names, as keys, and + * object implementing those interfaces, as values. + * @hide pending API council approval. + */ + protected WebView(Context context, AttributeSet attrs, int defStyle, + Map<String, Object> javascriptInterfaces) { super(context, attrs, defStyle); init(); mCallbackProxy = new CallbackProxy(context, this); - mWebViewCore = new WebViewCore(context, this, mCallbackProxy); + mWebViewCore = new WebViewCore(context, this, mCallbackProxy, javascriptInterfaces); mDatabase = WebViewDatabase.getInstance(context); - mFocusData = new WebViewCore.FocusData(); - mFocusData.mFrame = 0; - mFocusData.mNode = 0; - mFocusData.mX = 0; - mFocusData.mY = 0; mScroller = new Scroller(context); - initZoomController(context); - } + mViewManager = new ViewManager(this); - private void initZoomController(Context context) { - // Create the buttons controller mZoomButtonsController = new ZoomButtonsController(this); mZoomButtonsController.setOnZoomListener(mZoomListener); // ZoomButtonsController positions the buttons at the bottom, but in @@ -723,30 +743,11 @@ public class WebView extends AbsoluteLayout params; frameParams.gravity = Gravity.RIGHT; } - - // Create the accessory buttons - LayoutInflater inflater = - (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - ViewGroup container = mZoomButtonsController.getContainer(); - inflater.inflate(com.android.internal.R.layout.zoom_browser_accessory_buttons, container); - mZoomOverviewButton = - (ImageView) container.findViewById(com.android.internal.R.id.zoom_page_overview); - mZoomOverviewButton.setOnClickListener( - new View.OnClickListener() { - public void onClick(View v) { - mZoomButtonsController.setVisible(false); - zoomScrollOut(); - if (mLogEvent) { - Checkin.updateStats(mContext.getContentResolver(), - Checkin.Stats.Tag.BROWSER_ZOOM_OVERVIEW, 1, 0.0); - } - } - }); } private void updateZoomButtonsEnabled() { boolean canZoomIn = mActualScale < mMaxZoomScale; - boolean canZoomOut = mActualScale > mMinZoomScale; + boolean canZoomOut = mActualScale > mMinZoomScale && !mInZoomOverview; if (!canZoomIn && !canZoomOut) { // Hide the zoom in and out buttons, as well as the fit to page // button, if the page cannot zoom @@ -760,8 +761,6 @@ public class WebView extends AbsoluteLayout mZoomButtonsController.setZoomInEnabled(canZoomIn); mZoomButtonsController.setZoomOutEnabled(canZoomOut); } - mZoomOverviewButton.setVisibility(canZoomScrollOut() ? View.VISIBLE: - View.GONE); } private void init() { @@ -772,9 +771,11 @@ public class WebView extends AbsoluteLayout setLongClickable(true); final ViewConfiguration configuration = ViewConfiguration.get(getContext()); - final int slop = configuration.getScaledTouchSlop(); + int slop = configuration.getScaledTouchSlop(); mTouchSlopSquare = slop * slop; mMinLockSnapReverseDistance = slop; + slop = configuration.getScaledDoubleTapSlop(); + mDoubleTapSlopSquare = slop * slop; final float density = getContext().getResources().getDisplayMetrics().density; // use one line height, 16 based on our current default font, for how // far we allow a touch be away from the edge of a link @@ -908,8 +909,9 @@ public class WebView extends AbsoluteLayout /* * Return the width of the view where the content of WebView should render * to. + * Note: this can be called from WebCoreThread. */ - private int getViewWidth() { + /* package */ int getViewWidth() { if (!isVerticalScrollBarEnabled() || mOverlayVerticalScrollbar) { return getWidth(); } else { @@ -918,15 +920,35 @@ public class WebView extends AbsoluteLayout } /* + * returns the height of the titlebarview (if any). Does not care about + * scrolling + */ + private int getTitleHeight() { + return mTitleBar != null ? mTitleBar.getHeight() : 0; + } + + /* + * Return the amount of the titlebarview (if any) that is visible + */ + private int getVisibleTitleHeight() { + return Math.max(getTitleHeight() - mScrollY, 0); + } + + /* * Return the height of the view where the content of WebView should render - * to. + * to. Note that this excludes mTitleBar, if there is one. + * Note: this can be called from WebCoreThread. */ - private int getViewHeight() { - if (!isHorizontalScrollBarEnabled() || mOverlayHorizontalScrollbar) { - return getHeight(); - } else { - return getHeight() - getHorizontalScrollbarHeight(); + /* package */ int getViewHeight() { + return getViewHeightWithTitle() - getVisibleTitleHeight(); + } + + private int getViewHeightWithTitle() { + int height = getHeight(); + if (isHorizontalScrollBarEnabled() && !mOverlayHorizontalScrollbar) { + height -= getHorizontalScrollbarHeight(); } + return height; } /** @@ -996,7 +1018,7 @@ public class WebView extends AbsoluteLayout clearTextEntry(); if (mWebViewCore != null) { // Set the handlers to null before destroying WebViewCore so no - // more messages will be posted. + // more messages will be posted. mCallbackProxy.setWebViewClient(null); mCallbackProxy.setWebChromeClient(null); // Tell WebViewCore to destroy itself @@ -1027,12 +1049,23 @@ public class WebView extends AbsoluteLayout /** * If platform notifications are enabled, this should be called - * from onPause() or onStop(). + * from the Activity's onPause() or onStop(). */ public static void disablePlatformNotifications() { Network.disablePlatformNotifications(); } - + + /** + * Sets JavaScript engine flags. + * + * @param flags JS engine flags in a String + * + * @hide pending API solidification + */ + public void setJsFlags(String flags) { + mWebViewCore.sendMessage(EventHub.SET_JS_FLAGS, flags); + } + /** * Inform WebView of the network state. This is used to set * the javascript property window.navigator.isOnline and @@ -1045,7 +1078,7 @@ public class WebView extends AbsoluteLayout } /** - * Save the state of this WebView used in + * Save the state of this WebView used in * {@link android.app.Activity#onSaveInstanceState}. Please note that this * method no longer stores the display data for this WebView. The previous * behavior could potentially leak files if {@link #restoreState} was never @@ -1078,6 +1111,12 @@ public class WebView extends AbsoluteLayout ArrayList<byte[]> history = new ArrayList<byte[]>(size); for (int i = 0; i < size; i++) { WebHistoryItem item = list.getItemAtIndex(i); + if (null == item) { + // FIXME: this shouldn't happen + // need to determine how item got set to null + Log.w(LOGTAG, "saveState: Unexpected null history item."); + return null; + } byte[] data = item.getFlattenedData(); if (data == null) { // It would be very odd to not have any data for a given history @@ -1123,6 +1162,9 @@ public class WebView extends AbsoluteLayout b.putInt("scrollX", mScrollX); b.putInt("scrollY", mScrollY); b.putFloat("scale", mActualScale); + if (mInZoomOverview) { + b.putFloat("lastScale", mLastScale); + } return true; } return false; @@ -1167,6 +1209,13 @@ public class WebView extends AbsoluteLayout // onSizeChanged() is called, the rest will be set // correctly mActualScale = scale; + float lastScale = b.getFloat("lastScale", -1.0f); + if (lastScale > 0) { + mInZoomOverview = true; + mLastScale = lastScale; + } else { + mInZoomOverview = false; + } invalidate(); return true; } @@ -1176,10 +1225,10 @@ public class WebView extends AbsoluteLayout /** * Restore the state of this WebView from the given map used in - * {@link android.app.Activity#onRestoreInstanceState}. This method should - * be called to restore the state of the WebView before using the object. If - * it is called after the WebView has had a chance to build state (load - * pages, create a back/forward list, etc.) there may be undesirable + * {@link android.app.Activity#onRestoreInstanceState}. This method should + * be called to restore the state of the WebView before using the object. If + * it is called after the WebView has had a chance to build state (load + * pages, create a back/forward list, etc.) there may be undesirable * side-effects. Please note that this method no longer restores the * display data for this WebView. See {@link #savePicture} and {@link * #restorePicture} for saving and restoring the display data. @@ -1240,6 +1289,9 @@ public class WebView extends AbsoluteLayout * @param url The url of the resource to load. */ public void loadUrl(String url) { + if (url == null) { + return; + } switchOutDrawHistory(); mWebViewCore.sendMessage(EventHub.LOAD_URL, url); clearTextEntry(); @@ -1249,18 +1301,16 @@ public class WebView extends AbsoluteLayout * Load the url with postData using "POST" method into the WebView. If url * is not a network url, it will be loaded with {link * {@link #loadUrl(String)} instead. - * + * * @param url The url of the resource to load. * @param postData The data will be passed to "POST" request. - * - * @hide pending API solidification */ public void postUrl(String url, byte[] postData) { if (URLUtil.isNetworkUrl(url)) { switchOutDrawHistory(); - HashMap arg = new HashMap(); - arg.put("url", url); - arg.put("data", postData); + WebViewCore.PostUrlData arg = new WebViewCore.PostUrlData(); + arg.mUrl = url; + arg.mPostData = postData; mWebViewCore.sendMessage(EventHub.POST_URL, arg); clearTextEntry(); } else { @@ -1294,7 +1344,7 @@ public class WebView extends AbsoluteLayout * able to access asset files. If the baseUrl is anything other than * http(s)/ftp(s)/about/javascript as scheme, you can access asset files for * sub resources. - * + * * @param baseUrl Url to resolve relative paths with, if null defaults to * "about:blank" * @param data A String of data in the given encoding. @@ -1305,18 +1355,18 @@ public class WebView extends AbsoluteLayout */ public void loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String failUrl) { - + if (baseUrl != null && baseUrl.toLowerCase().startsWith("data:")) { loadData(data, mimeType, encoding); return; } switchOutDrawHistory(); - HashMap arg = new HashMap(); - arg.put("baseUrl", baseUrl); - arg.put("data", data); - arg.put("mimeType", mimeType); - arg.put("encoding", encoding); - arg.put("failUrl", failUrl); + WebViewCore.BaseUrlData arg = new WebViewCore.BaseUrlData(); + arg.mBaseUrl = baseUrl; + arg.mData = data; + arg.mMimeType = mimeType; + arg.mEncoding = encoding; + arg.mFailUrl = failUrl; mWebViewCore.sendMessage(EventHub.LOAD_DATA, arg); clearTextEntry(); } @@ -1335,6 +1385,7 @@ public class WebView extends AbsoluteLayout * Reload the current url. */ public void reload() { + clearTextEntry(); switchOutDrawHistory(); mWebViewCore.sendMessage(EventHub.RELOAD); } @@ -1426,7 +1477,7 @@ public class WebView extends AbsoluteLayout ignoreSnapshot ? 1 : 0); } } - + private boolean extendScroll(int y) { int finalY = mScroller.getFinalY(); int newY = pinLocY(finalY + y); @@ -1435,7 +1486,7 @@ public class WebView extends AbsoluteLayout mScroller.extendDuration(computeDuration(0, y)); return true; } - + /** * Scroll the contents of the view up by half the view size * @param top true to jump to the top of the page @@ -1445,7 +1496,7 @@ public class WebView extends AbsoluteLayout if (mNativeClass == 0) { return false; } - nativeClearFocus(-1, -1); + nativeClearCursor(); // start next trackball movement from page edge if (top) { // go to the top of the document return pinScrollTo(mScrollX, 0, true, 0); @@ -1459,10 +1510,10 @@ public class WebView extends AbsoluteLayout y = -h / 2; } mUserScroll = true; - return mScroller.isFinished() ? pinScrollBy(0, y, true, 0) + return mScroller.isFinished() ? pinScrollBy(0, y, true, 0) : extendScroll(y); } - + /** * Scroll the contents of the view down by half the page size * @param bottom true to jump to bottom of page @@ -1472,7 +1523,7 @@ public class WebView extends AbsoluteLayout if (mNativeClass == 0) { return false; } - nativeClearFocus(-1, -1); + nativeClearCursor(); // start next trackball movement from page edge if (bottom) { return pinScrollTo(mScrollX, mContentHeight, true, 0); } @@ -1485,7 +1536,7 @@ public class WebView extends AbsoluteLayout y = h / 2; } mUserScroll = true; - return mScroller.isFinished() ? pinScrollBy(0, y, true, 0) + return mScroller.isFinished() ? pinScrollBy(0, y, true, 0) : extendScroll(y); } @@ -1498,7 +1549,7 @@ public class WebView extends AbsoluteLayout mContentHeight = 0; mWebViewCore.sendMessage(EventHub.CLEAR_CONTENT); } - + /** * Return a new picture that captures the current display of the webview. * This is a copy of the display, and will be unaffected if the webview @@ -1509,7 +1560,7 @@ public class WebView extends AbsoluteLayout * bounds of the view. */ public Picture capturePicture() { - if (null == mWebViewCore) return null; // check for out of memory tab + if (null == mWebViewCore) return null; // check for out of memory tab return mWebViewCore.copyContentPicture(); } @@ -1517,17 +1568,17 @@ public class WebView extends AbsoluteLayout * Return true if the browser is displaying a TextView for text input. */ private boolean inEditingMode() { - return mTextEntry != null && mTextEntry.getParent() != null - && mTextEntry.hasFocus(); + return mWebTextView != null && mWebTextView.getParent() != null + && mWebTextView.hasFocus(); } private void clearTextEntry() { if (inEditingMode()) { - mTextEntry.remove(); + mWebTextView.remove(); } } - /** + /** * Return the current scale of the WebView * @return The current scale. */ @@ -1568,7 +1619,7 @@ public class WebView extends AbsoluteLayout } /** - * Return a HitTestResult based on the current focus node. If a HTML::a tag + * Return a HitTestResult based on the current cursor node. If a HTML::a tag * is found and the anchor has a non-javascript url, the HitTestResult type * is set to SRC_ANCHOR_TYPE and the url is set in the "extra" field. If the * anchor does not have a url or if it is a javascript url, the type will @@ -1591,26 +1642,26 @@ public class WebView extends AbsoluteLayout } HitTestResult result = new HitTestResult(); - - if (nativeUpdateFocusNode()) { - FocusNode node = mFocusNode; - if (node.mIsTextField || node.mIsTextArea) { + if (nativeHasCursorNode()) { + if (nativeCursorIsTextInput()) { result.setType(HitTestResult.EDIT_TEXT_TYPE); - } else if (node.mText != null) { - String text = node.mText; - if (text.startsWith(SCHEME_TEL)) { - result.setType(HitTestResult.PHONE_TYPE); - result.setExtra(text.substring(SCHEME_TEL.length())); - } else if (text.startsWith(SCHEME_MAILTO)) { - result.setType(HitTestResult.EMAIL_TYPE); - result.setExtra(text.substring(SCHEME_MAILTO.length())); - } else if (text.startsWith(SCHEME_GEO)) { - result.setType(HitTestResult.GEO_TYPE); - result.setExtra(URLDecoder.decode(text - .substring(SCHEME_GEO.length()))); - } else if (node.mIsAnchor) { - result.setType(HitTestResult.SRC_ANCHOR_TYPE); - result.setExtra(text); + } else { + String text = nativeCursorText(); + if (text != null) { + if (text.startsWith(SCHEME_TEL)) { + result.setType(HitTestResult.PHONE_TYPE); + result.setExtra(text.substring(SCHEME_TEL.length())); + } else if (text.startsWith(SCHEME_MAILTO)) { + result.setType(HitTestResult.EMAIL_TYPE); + result.setExtra(text.substring(SCHEME_MAILTO.length())); + } else if (text.startsWith(SCHEME_GEO)) { + result.setType(HitTestResult.GEO_TYPE); + result.setExtra(URLDecoder.decode(text + .substring(SCHEME_GEO.length()))); + } else if (nativeCursorIsAnchor()) { + result.setType(HitTestResult.SRC_ANCHOR_TYPE); + result.setExtra(text); + } } } } @@ -1618,12 +1669,12 @@ public class WebView extends AbsoluteLayout if (type == HitTestResult.UNKNOWN_TYPE || type == HitTestResult.SRC_ANCHOR_TYPE) { // Now check to see if it is an image. - int contentX = viewToContent((int) mLastTouchX + mScrollX); - int contentY = viewToContent((int) mLastTouchY + mScrollY); + int contentX = viewToContentX((int) mLastTouchX + mScrollX); + int contentY = viewToContentY((int) mLastTouchY + mScrollY); String text = nativeImageURI(contentX, contentY); if (text != null) { - result.setType(type == HitTestResult.UNKNOWN_TYPE ? - HitTestResult.IMAGE_TYPE : + result.setType(type == HitTestResult.UNKNOWN_TYPE ? + HitTestResult.IMAGE_TYPE : HitTestResult.SRC_IMAGE_ANCHOR_TYPE); result.setExtra(text); } @@ -1635,37 +1686,36 @@ public class WebView extends AbsoluteLayout * Request the href of an anchor element due to getFocusNodePath returning * "href." If hrefMsg is null, this method returns immediately and does not * dispatch hrefMsg to its target. - * + * * @param hrefMsg This message will be dispatched with the result of the * request as the data member with "url" as key. The result can * be null. */ + // FIXME: API change required to change the name of this function. We now + // look at the cursor node, and not the focus node. Also, what is + // getFocusNodePath? public void requestFocusNodeHref(Message hrefMsg) { if (hrefMsg == null || mNativeClass == 0) { return; } - if (nativeUpdateFocusNode()) { - FocusNode node = mFocusNode; - if (node.mIsAnchor) { - // NOTE: We may already have the url of the anchor stored in - // node.mText but it may be out of date or the caller may want - // to know about javascript urls. - mWebViewCore.sendMessage(EventHub.REQUEST_FOCUS_HREF, - node.mFramePointer, node.mNodePointer, hrefMsg); - } + if (nativeCursorIsAnchor()) { + mWebViewCore.sendMessage(EventHub.REQUEST_CURSOR_HREF, + nativeCursorFramePointer(), nativeCursorNodePointer(), + hrefMsg); } } - + /** * Request the url of the image last touched by the user. msg will be sent * to its target with a String representing the url as its object. - * + * * @param msg This message will be dispatched with the result of the request * as the data member with "url" as key. The result can be null. */ public void requestImageRef(Message msg) { - int contentX = viewToContent((int) mLastTouchX + mScrollX); - int contentY = viewToContent((int) mLastTouchY + mScrollY); + if (0 == mNativeClass) return; // client isn't initialized + int contentX = viewToContentX((int) mLastTouchX + mScrollX); + int contentY = viewToContentY((int) mLastTouchY + mScrollY); String ref = nativeImageURI(contentX, contentY); Bundle data = msg.getData(); data.putString("url", ref); @@ -1696,34 +1746,160 @@ public class WebView extends AbsoluteLayout // Expects y in view coordinates private int pinLocY(int y) { - return pinLoc(y, getViewHeight(), computeVerticalScrollRange()); + int titleH = getTitleHeight(); + // if the titlebar is still visible, just pin against 0 + if (y <= titleH) { + return Math.max(y, 0); + } + // convert to 0-based coordinate (subtract the title height) + // pin(), and then add the title height back in + return pinLoc(y - titleH, getViewHeight(), + computeVerticalScrollRange()) + titleH; + } + + /** + * A title bar which is embedded in this WebView, and scrolls along with it + * vertically, but not horizontally. + */ + private View mTitleBar; + + /** + * Since we draw the title bar ourselves, we removed the shadow from the + * browser's activity. We do want a shadow at the bottom of the title bar, + * or at the top of the screen if the title bar is not visible. This + * drawable serves that purpose. + */ + private Drawable mTitleShadow; + + /** + * Add or remove a title bar to be embedded into the WebView, and scroll + * along with it vertically, while remaining in view horizontally. Pass + * null to remove the title bar from the WebView, and return to drawing + * the WebView normally without translating to account for the title bar. + * @hide + */ + public void setEmbeddedTitleBar(View v) { + if (mTitleBar == v) return; + if (mTitleBar != null) { + removeView(mTitleBar); + } + if (null != v) { + addView(v, new AbsoluteLayout.LayoutParams( + ViewGroup.LayoutParams.FILL_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT, 0, 0)); + if (mTitleShadow == null) { + mTitleShadow = (Drawable) mContext.getResources().getDrawable( + com.android.internal.R.drawable.title_bar_shadow); + } + } + mTitleBar = v; + } + + /** + * Given a distance in view space, convert it to content space. Note: this + * does not reflect translation, just scaling, so this should not be called + * with coordinates, but should be called for dimensions like width or + * height. + */ + private int viewToContentDimension(int d) { + return Math.round(d * mInvActualScale); + } + + /** + * Given an x coordinate in view space, convert it to content space. Also + * may be used for absolute heights (such as for the WebTextView's + * textSize, which is unaffected by the height of the title bar). + */ + /*package*/ int viewToContentX(int x) { + return viewToContentDimension(x); + } + + /** + * Given a y coordinate in view space, convert it to content space. + * Takes into account the height of the title bar if there is one + * embedded into the WebView. + */ + /*package*/ int viewToContentY(int y) { + return viewToContentDimension(y - getTitleHeight()); + } + + /** + * Given a distance in content space, convert it to view space. Note: this + * does not reflect translation, just scaling, so this should not be called + * with coordinates, but should be called for dimensions like width or + * height. + */ + /*package*/ int contentToViewDimension(int d) { + return Math.round(d * mActualScale); + } + + /** + * Given an x coordinate in content space, convert it to view + * space. + */ + /*package*/ int contentToViewX(int x) { + return contentToViewDimension(x); } - /*package*/ int viewToContent(int x) { - return Math.round(x * mInvActualScale); + /** + * Given a y coordinate in content space, convert it to view + * space. Takes into account the height of the title bar. + */ + /*package*/ int contentToViewY(int y) { + return contentToViewDimension(y) + getTitleHeight(); } - private int contentToView(int x) { - return Math.round(x * mActualScale); + private Rect contentToViewRect(Rect x) { + return new Rect(contentToViewX(x.left), contentToViewY(x.top), + contentToViewX(x.right), contentToViewY(x.bottom)); } + /* To invalidate a rectangle in content coordinates, we need to transform + the rect into view coordinates, so we can then call invalidate(...). + + Normally, we would just call contentToView[XY](...), which eventually + calls Math.round(coordinate * mActualScale). However, for invalidates, + we need to account for the slop that occurs with antialiasing. To + address that, we are a little more liberal in the size of the rect that + we invalidate. + + This liberal calculation calls floor() for the top/left, and ceil() for + the bottom/right coordinates. This catches the possible extra pixels of + antialiasing that we might have missed with just round(). + */ + // Called by JNI to invalidate the View, given rectangle coordinates in // content space private void viewInvalidate(int l, int t, int r, int b) { - invalidate(contentToView(l), contentToView(t), contentToView(r), - contentToView(b)); + final float scale = mActualScale; + final int dy = getTitleHeight(); + invalidate((int)Math.floor(l * scale), + (int)Math.floor(t * scale) + dy, + (int)Math.ceil(r * scale), + (int)Math.ceil(b * scale) + dy); } // Called by JNI to invalidate the View after a delay, given rectangle // coordinates in content space private void viewInvalidateDelayed(long delay, int l, int t, int r, int b) { - postInvalidateDelayed(delay, contentToView(l), contentToView(t), - contentToView(r), contentToView(b)); + final float scale = mActualScale; + final int dy = getTitleHeight(); + postInvalidateDelayed(delay, + (int)Math.floor(l * scale), + (int)Math.floor(t * scale) + dy, + (int)Math.ceil(r * scale), + (int)Math.ceil(b * scale) + dy); } - private Rect contentToView(Rect x) { - return new Rect(contentToView(x.left), contentToView(x.top) - , contentToView(x.right), contentToView(x.bottom)); + private void invalidateContentRect(Rect r) { + viewInvalidate(r.left, r.top, r.right, r.bottom); + } + + // stop the scroll animation, and don't let a subsequent fling add + // to the existing velocity + private void abortAnimation() { + mScroller.abortAnimation(); + mLastVelocity = 0; } /* call from webcoreview.draw(), so we're still executing in the UI thread @@ -1734,7 +1910,7 @@ public class WebView extends AbsoluteLayout if ((w | h) == 0) { return; } - + // don't abort a scroll animation if we didn't change anything if (mContentWidth != w || mContentHeight != h) { // record new dimensions @@ -1748,12 +1924,15 @@ public class WebView extends AbsoluteLayout int oldY = mScrollY; mScrollX = pinLocX(mScrollX); mScrollY = pinLocY(mScrollY); - // android.util.Log.d("skia", "recordNewContentSize - - // abortAnimation"); - mScroller.abortAnimation(); // just in case if (oldX != mScrollX || oldY != mScrollY) { sendOurVisibleRect(); } + if (!mScroller.isFinished()) { + // We are in the middle of a scroll. Repin the final scroll + // position. + mScroller.setFinalX(pinLocX(mScroller.getFinalX())); + mScroller.setFinalY(pinLocY(mScroller.getFinalY())); + } } } contentSizeChanged(updateLayout); @@ -1785,7 +1964,8 @@ public class WebView extends AbsoluteLayout int oldY = mScrollY; float ratio = scale * mInvActualScale; // old inverse float sx = ratio * oldX + (ratio - 1) * mZoomCenterX; - float sy = ratio * oldY + (ratio - 1) * mZoomCenterY; + float sy = ratio * oldY + (ratio - 1) + * (mZoomCenterY - getTitleHeight()); // now update our new scale and inverse if (scale != mActualScale && !mPreviewZoomOnly) { @@ -1794,7 +1974,10 @@ public class WebView extends AbsoluteLayout mActualScale = scale; mInvActualScale = 1 / scale; - // as we don't have animation for scaling, don't do animation + // Scale all the child views + mViewManager.scaleAll(); + + // as we don't have animation for scaling, don't do animation // for scrolling, as it causes weird intermediate state // pinScrollTo(Math.round(sx), Math.round(sy)); mScrollX = pinLocX(Math.round(sx)); @@ -1815,18 +1998,21 @@ public class WebView extends AbsoluteLayout private Rect sendOurVisibleRect() { Rect rect = new Rect(); calcOurContentVisibleRect(rect); - if (mFindIsUp) { - rect.bottom -= viewToContent(FIND_HEIGHT); - } // Rect.equals() checks for null input. if (!rect.equals(mLastVisibleRectSent)) { + Point pos = new Point(rect.left, rect.top); mWebViewCore.sendMessage(EventHub.SET_SCROLL_OFFSET, - rect.left, rect.top); + nativeMoveGeneration(), 0, pos); mLastVisibleRectSent = rect; } Rect globalRect = new Rect(); if (getGlobalVisibleRect(globalRect) && !globalRect.equals(mLastGlobalRect)) { + if (DebugFlags.WEB_VIEW) { + Log.v(LOGTAG, "sendOurVisibleRect=(" + globalRect.left + "," + + globalRect.top + ",r=" + globalRect.right + ",b=" + + globalRect.bottom); + } // TODO: the global offset is only used by windowRect() // in ChromeClientAndroid ; other clients such as touch // and mouse events could return view + screen relative points. @@ -1841,15 +2027,30 @@ public class WebView extends AbsoluteLayout Point p = new Point(); getGlobalVisibleRect(r, p); r.offset(-p.x, -p.y); + if (mFindIsUp) { + r.bottom -= mFindHeight; + } } // Sets r to be our visible rectangle in content coordinates private void calcOurContentVisibleRect(Rect r) { calcOurVisibleRect(r); - r.left = viewToContent(r.left); - r.top = viewToContent(r.top); - r.right = viewToContent(r.right); - r.bottom = viewToContent(r.bottom); + r.left = viewToContentX(r.left); + // viewToContentY will remove the total height of the title bar. Add + // the visible height back in to account for the fact that if the title + // bar is partially visible, the part of the visible rect which is + // displaying our content is displaced by that amount. + r.top = viewToContentY(r.top + getVisibleTitleHeight()); + r.right = viewToContentX(r.right); + r.bottom = viewToContentY(r.bottom); + } + + static class ViewSizeData { + int mWidth; + int mHeight; + int mTextWrapWidth; + float mScale; + boolean mIgnoreHeight; } /** @@ -1859,7 +2060,8 @@ public class WebView extends AbsoluteLayout * @return true if new values were sent */ private boolean sendViewSizeZoom() { - int newWidth = Math.round(getViewWidth() * mInvActualScale); + int viewWidth = getViewWidth(); + int newWidth = Math.round(viewWidth * mInvActualScale); int newHeight = Math.round(getViewHeight() * mInvActualScale); /* * Because the native side may have already done a layout before the @@ -1874,8 +2076,17 @@ public class WebView extends AbsoluteLayout } // Avoid sending another message if the dimensions have not changed. if (newWidth != mLastWidthSent || newHeight != mLastHeightSent) { - mWebViewCore.sendMessage(EventHub.VIEW_SIZE_CHANGED, - newWidth, newHeight, new Float(mActualScale)); + ViewSizeData data = new ViewSizeData(); + data.mWidth = newWidth; + data.mHeight = newHeight; + // while in zoom overview mode, the text are wrapped to the screen + // width matching mLastScale. So that we don't trigger re-flow while + // toggling between overview mode and normal mode. + data.mTextWrapWidth = mInZoomOverview ? Math.round(viewWidth + / mLastScale) : newWidth; + data.mScale = mActualScale; + data.mIgnoreHeight = mZoomScale != 0 && !mHeightCanMeasure; + mWebViewCore.sendMessage(EventHub.VIEW_SIZE_CHANGED, data); mLastWidthSent = newWidth; mLastHeightSent = newHeight; return true; @@ -1888,26 +2099,40 @@ public class WebView extends AbsoluteLayout if (mDrawHistory) { return mHistoryWidth; } else { - return contentToView(mContentWidth); + // to avoid rounding error caused unnecessary scrollbar, use floor + return (int) Math.floor(mContentWidth * mActualScale); } } - // Make sure this stays in sync with the actual height of the FindDialog. - private static final int FIND_HEIGHT = 79; - @Override protected int computeVerticalScrollRange() { if (mDrawHistory) { return mHistoryHeight; } else { - int height = contentToView(mContentHeight); - if (mFindIsUp) { - height += FIND_HEIGHT; - } - return height; + // to avoid rounding error caused unnecessary scrollbar, use floor + return (int) Math.floor(mContentHeight * mActualScale); } } + @Override + protected int computeVerticalScrollOffset() { + return Math.max(mScrollY - getTitleHeight(), 0); + } + + @Override + protected int computeVerticalScrollExtent() { + return getViewHeight(); + } + + /** @hide */ + @Override + protected void onDrawVerticalScrollBar(Canvas canvas, + Drawable scrollBar, + int l, int t, int r, int b) { + scrollBar.setBounds(l, t + getVisibleTitleHeight(), r, b); + scrollBar.draw(canvas); + } + /** * Get the url for the current page. This is not always the same as the url * passed to WebViewClient.onPageStarted because although the load for @@ -1918,10 +2143,10 @@ public class WebView extends AbsoluteLayout WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); return h != null ? h.getUrl() : null; } - + /** - * Get the original url for the current page. This is not always the same - * as the url passed to WebViewClient.onPageStarted because although the + * Get the original url for the current page. This is not always the same + * as the url passed to WebViewClient.onPageStarted because although the * load for that url has begun, the current page may not have changed. * Also, there may have been redirects resulting in a different url to that * originally requested. @@ -1953,13 +2178,22 @@ public class WebView extends AbsoluteLayout } /** + * Get the touch icon url for the apple-touch-icon <link> element. + * @hide + */ + public String getTouchIconUrl() { + WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); + return h != null ? h.getTouchIconUrl() : null; + } + + /** * Get the progress for the current page. * @return The progress for the current page between 0 and 100. */ public int getProgress() { return mCallbackProxy.getProgress(); } - + /** * @return the height of the HTML content. */ @@ -1968,22 +2202,75 @@ public class WebView extends AbsoluteLayout } /** - * Pause all layout, parsing, and javascript timers. This can be useful if - * the WebView is not visible or the application has been paused. + * @return the width of the HTML content. + * @hide + */ + public int getContentWidth() { + return mContentWidth; + } + + /** + * Pause all layout, parsing, and javascript timers for all webviews. This + * is a global requests, not restricted to just this webview. This can be + * useful if the application has been paused. */ public void pauseTimers() { mWebViewCore.sendMessage(EventHub.PAUSE_TIMERS); } /** - * Resume all layout, parsing, and javascript timers. This will resume - * dispatching all timers. + * Resume all layout, parsing, and javascript timers for all webviews. + * This will resume dispatching all timers. */ public void resumeTimers() { mWebViewCore.sendMessage(EventHub.RESUME_TIMERS); } /** + * Call this to pause any extra processing associated with this view and + * its associated DOM/plugins/javascript/etc. For example, if the view is + * taken offscreen, this could be called to reduce unnecessary CPU and/or + * network traffic. When the view is again "active", call onResume(). + * + * Note that this differs from pauseTimers(), which affects all views/DOMs + * @hide + */ + public void onPause() { + if (!mIsPaused) { + mIsPaused = true; + mWebViewCore.sendMessage(EventHub.ON_PAUSE); + } + } + + /** + * Call this to balanace a previous call to onPause() + * @hide + */ + public void onResume() { + if (mIsPaused) { + mIsPaused = false; + mWebViewCore.sendMessage(EventHub.ON_RESUME); + } + } + + /** + * Returns true if the view is paused, meaning onPause() was called. Calling + * onResume() sets the paused state back to false. + * @hide + */ + public boolean isPaused() { + return mIsPaused; + } + + /** + * Call this to inform the view that memory is low so that it can + * free any available memory. + */ + public void freeMemory() { + mWebViewCore.sendMessage(EventHub.FREE_MEMORY); + } + + /** * Clear the resource cache. Note that the cache is per-application, so * this will clear the cache for all WebViews used. * @@ -2004,7 +2291,7 @@ public class WebView extends AbsoluteLayout public void clearFormData() { if (inEditingMode()) { AutoCompleteAdapter adapter = null; - mTextEntry.setAdapterCustom(adapter); + mWebTextView.setAdapterCustom(adapter); } } @@ -2038,12 +2325,13 @@ public class WebView extends AbsoluteLayout /* * Highlight and scroll to the next occurance of String in findAll. - * Wraps the page infinitely, and scrolls. Must be called after + * Wraps the page infinitely, and scrolls. Must be called after * calling findAll. * * @param forward Direction to search. */ public void findNext(boolean forward) { + if (0 == mNativeClass) return; // client isn't initialized nativeFindNext(forward); } @@ -2054,7 +2342,12 @@ public class WebView extends AbsoluteLayout * that were found. */ public int findAll(String find) { - mFindIsUp = true; + if (0 == mNativeClass) return 0; // client isn't initialized + if (mFindIsUp == false) { + recordNewContentSize(mContentWidth, mContentHeight + mFindHeight, + false); + mFindIsUp = true; + } int result = nativeFindAll(find.toLowerCase(), find.toUpperCase()); invalidate(); return result; @@ -2063,12 +2356,10 @@ public class WebView extends AbsoluteLayout // Used to know whether the find dialog is open. Affects whether // or not we draw the highlights for matches. private boolean mFindIsUp; + private int mFindHeight; - private native int nativeFindAll(String findLower, String findUpper); - private native void nativeFindNext(boolean forward); - /** - * Return the first substring consisting of the address of a physical + * Return the first substring consisting of the address of a physical * location. Currently, only addresses in the United States are detected, * and consist of: * - a house number @@ -2081,21 +2372,51 @@ public class WebView extends AbsoluteLayout * All names must be correctly capitalized, and the zip code, if present, * must be valid for the state. The street type must be a standard USPS * spelling or abbreviation. The state or territory must also be spelled - * or abbreviated using USPS standards. The house number may not exceed + * or abbreviated using USPS standards. The house number may not exceed * five digits. * @param addr The string to search for addresses. * * @return the address, or if no address is found, return null. */ public static String findAddress(String addr) { - return WebViewCore.nativeFindAddress(addr); + return findAddress(addr, false); + } + + /** + * @hide + * Return the first substring consisting of the address of a physical + * location. Currently, only addresses in the United States are detected, + * and consist of: + * - a house number + * - a street name + * - a street type (Road, Circle, etc), either spelled out or abbreviated + * - a city name + * - a state or territory, either spelled out or two-letter abbr. + * - an optional 5 digit or 9 digit zip code. + * + * Names are optionally capitalized, and the zip code, if present, + * must be valid for the state. The street type must be a standard USPS + * spelling or abbreviation. The state or territory must also be spelled + * or abbreviated using USPS standards. The house number may not exceed + * five digits. + * @param addr The string to search for addresses. + * @param caseInsensitive addr Set to true to make search ignore case. + * + * @return the address, or if no address is found, return null. + */ + public static String findAddress(String addr, boolean caseInsensitive) { + return WebViewCore.nativeFindAddress(addr, caseInsensitive); } /* * Clear the highlighting surrounding text matches created by findAll. */ public void clearMatches() { - mFindIsUp = false; + if (mFindIsUp) { + recordNewContentSize(mContentWidth, mContentHeight - mFindHeight, + false); + mFindIsUp = false; + } nativeSetFindIsDown(); // Now that the dialog has been removed, ensure that we scroll to a // location that is not beyond the end of the page. @@ -2104,6 +2425,16 @@ public class WebView extends AbsoluteLayout } /** + * @hide + */ + public void setFindDialogHeight(int height) { + if (DebugFlags.WEB_VIEW) { + Log.v(LOGTAG, "setFindDialogHeight height=" + height); + } + mFindHeight = height; + } + + /** * Query the document to see if it contains any image references. The * message object will be dispatched with arg1 being set to 1 if images * were found and 0 if the document does not reference any images. @@ -2145,7 +2476,6 @@ public class WebView extends AbsoluteLayout private boolean pinScrollBy(int dx, int dy, boolean animate, int animationDuration) { return pinScrollTo(mScrollX + dx, mScrollY + dy, animate, animationDuration); } - // helper to pin the scrollTo parameters (already in view coordinates) // returns true if the scroll was changed private boolean pinScrollTo(int x, int y, boolean animate, int animationDuration) { @@ -2157,15 +2487,14 @@ public class WebView extends AbsoluteLayout if ((dx | dy) == 0) { return false; } - - if (true && animate) { + if (animate) { // Log.d(LOGTAG, "startScroll: " + dx + " " + dy); - mScroller.startScroll(mScrollX, mScrollY, dx, dy, animationDuration > 0 ? animationDuration : computeDuration(dx, dy)); + awakenScrollBars(mScroller.getDuration()); invalidate(); } else { - mScroller.abortAnimation(); // just in case + abortAnimation(); // just in case scrollTo(x, y); } return true; @@ -2173,16 +2502,16 @@ public class WebView extends AbsoluteLayout // Scale from content to view coordinates, and pin. // Also called by jni webview.cpp - private void setContentScrollBy(int cx, int cy, boolean animate) { + private boolean setContentScrollBy(int cx, int cy, boolean animate) { if (mDrawHistory) { // disallow WebView to change the scroll position as History Picture // is used in the view system. // TODO: as we switchOutDrawHistory when trackball or navigation // keys are hit, this should be safe. Right? - return; + return false; } - cx = contentToView(cx); - cy = contentToView(cy); + cx = contentToViewDimension(cx); + cy = contentToViewDimension(cy); if (mHeightCanMeasure) { // move our visible rect according to scroll request if (cy != 0) { @@ -2196,17 +2525,18 @@ public class WebView extends AbsoluteLayout // FIXME: Why do we only scroll horizontally if there is no // vertical scroll? // Log.d(LOGTAG, "setContentScrollBy cy=" + cy); - if (cy == 0 && cx != 0) { - pinScrollBy(cx, 0, animate, 0); - } + return cy == 0 && cx != 0 && pinScrollBy(cx, 0, animate, 0); } else { - pinScrollBy(cx, cy, animate, 0); + return pinScrollBy(cx, cy, animate, 0); } } // scale from content to view coordinates, and pin - // return true if pin caused the final x/y different than the request cx/cy; - // return false if the view scroll to the exact position as it is requested. + // return true if pin caused the final x/y different than the request cx/cy, + // and a future scroll may reach the request cx/cy after our size has + // changed + // return false if the view scroll to the exact position as it is requested, + // where negative numbers are taken to mean 0 private boolean setContentScrollTo(int cx, int cy) { if (mDrawHistory) { // disallow WebView to change the scroll position as History Picture @@ -2216,12 +2546,35 @@ public class WebView extends AbsoluteLayout // saved scroll position, it is ok to skip this. return false; } - int vx = contentToView(cx); - int vy = contentToView(cy); + int vx; + int vy; + if ((cx | cy) == 0) { + // If the page is being scrolled to (0,0), do not add in the title + // bar's height, and simply scroll to (0,0). (The only other work + // in contentToView_ is to multiply, so this would not change 0.) + vx = 0; + vy = 0; + } else { + vx = contentToViewX(cx); + vy = contentToViewY(cy); + } // Log.d(LOGTAG, "content scrollTo [" + cx + " " + cy + "] view=[" + // vx + " " + vy + "]"); + // Some mobile sites attempt to scroll the title bar off the page by + // scrolling to (0,1). If we are at the top left corner of the + // page, assume this is an attempt to scroll off the title bar, and + // animate the title bar off screen slowly enough that the user can see + // it. + if (cx == 0 && cy == 1 && mScrollX == 0 && mScrollY == 0) { + pinScrollTo(vx, vy, true, SLIDE_TITLE_DURATION); + // Since we are animating, we have not yet reached the desired + // scroll position. Do not return true to request another attempt + return false; + } pinScrollTo(vx, vy, false, 0); - if (mScrollX != vx || mScrollY != vy) { + // If the request was to scroll to a negative coordinate, treat it as if + // it was a request to scroll to 0 + if ((mScrollX != vx && cx >= 0) || (mScrollY != vy && cy >= 0)) { return true; } else { return false; @@ -2235,8 +2588,8 @@ public class WebView extends AbsoluteLayout // is used in the view system. return; } - int vx = contentToView(cx); - int vy = contentToView(cy); + int vx = contentToViewX(cx); + int vy = contentToViewY(cy); pinScrollTo(vx, vy, true, 0); } @@ -2253,12 +2606,12 @@ public class WebView extends AbsoluteLayout } if (mHeightCanMeasure) { - if (getMeasuredHeight() != contentToView(mContentHeight) + if (getMeasuredHeight() != contentToViewDimension(mContentHeight) && updateLayout) { requestLayout(); } } else if (mWidthCanMeasure) { - if (getMeasuredWidth() != contentToView(mContentWidth) + if (getMeasuredWidth() != contentToViewDimension(mContentWidth) && updateLayout) { requestLayout(); } @@ -2299,6 +2652,16 @@ public class WebView extends AbsoluteLayout } /** + * Gets the chrome handler. + * @return the current WebChromeClient instance. + * + * @hide API council approval. + */ + public WebChromeClient getWebChromeClient() { + return mCallbackProxy.getWebChromeClient(); + } + + /** * Set the Picture listener. This is an interface used to receive * notifications of a new Picture. * @param listener An implementation of WebView.PictureListener. @@ -2343,10 +2706,9 @@ public class WebView extends AbsoluteLayout * @param interfaceName The name to used to expose the class in Javascript */ public void addJavascriptInterface(Object obj, String interfaceName) { - // Use Hashmap rather than Bundle as Bundles can't cope with Objects - HashMap arg = new HashMap(); - arg.put("object", obj); - arg.put("interfaceName", interfaceName); + WebViewCore.JSInterfaceData arg = new WebViewCore.JSInterfaceData(); + arg.mObject = obj; + arg.mInterfaceName = interfaceName; mWebViewCore.sendMessage(EventHub.ADD_JS_INTERFACE, arg); } @@ -2363,26 +2725,19 @@ public class WebView extends AbsoluteLayout /** * Return the list of currently loaded plugins. * @return The list of currently loaded plugins. + * + * @deprecated This was used for Gears, which has been deprecated. */ + @Deprecated public static synchronized PluginList getPluginList() { - if (sPluginList == null) { - sPluginList = new PluginList(); - } - return sPluginList; + return null; } /** - * Signal the WebCore thread to refresh its list of plugins. Use - * this if the directory contents of one of the plugin directories - * has been modified and needs its changes reflecting. May cause - * plugin load and/or unload. - * @param reloadOpenPages Set to true to reload all open pages. + * @deprecated This was used for Gears, which has been deprecated. */ - public void refreshPlugins(boolean reloadOpenPages) { - if (mWebViewCore != null) { - mWebViewCore.sendMessage(EventHub.REFRESH_PLUGINS, reloadOpenPages); - } - } + @Deprecated + public void refreshPlugins(boolean reloadOpenPages) { } //------------------------------------------------------------------------- // Override View methods @@ -2390,44 +2745,56 @@ public class WebView extends AbsoluteLayout @Override protected void finalize() throws Throwable { - destroy(); + try { + destroy(); + } finally { + super.finalize(); + } } - + + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + if (child == mTitleBar) { + // When drawing the title bar, move it horizontally to always show + // at the top of the WebView. + mTitleBar.offsetLeftAndRight(mScrollX - mTitleBar.getLeft()); + } + return super.drawChild(canvas, child, drawingTime); + } + @Override protected void onDraw(Canvas canvas) { // if mNativeClass is 0, the WebView has been destroyed. Do nothing. if (mNativeClass == 0) { return; } - if (mWebViewCore.mEndScaleZoom) { - mWebViewCore.mEndScaleZoom = false; - if (mTouchMode >= FIRST_SCROLL_ZOOM - && mTouchMode <= LAST_SCROLL_ZOOM) { - setHorizontalScrollBarEnabled(true); - setVerticalScrollBarEnabled(true); - mTouchMode = TOUCH_DONE_MODE; - } + int saveCount = canvas.save(); + if (mTitleBar != null) { + canvas.translate(0, (int) mTitleBar.getHeight()); + } + // Update the buttons in the picture, so when we draw the picture + // to the screen, they are in the correct state. + // Tell the native side if user is a) touching the screen, + // b) pressing the trackball down, or c) pressing the enter key + // If the cursor is on a button, we need to draw it in the pressed + // state. + // If mNativeClass is 0, we should not reach here, so we do not + // need to check it again. + nativeRecordButtons(hasFocus() && hasWindowFocus(), + mTouchMode == TOUCH_SHORTPRESS_START_MODE + || mTrackballDown || mGotCenterDown, false); + drawCoreAndCursorRing(canvas, mBackgroundColor, mDrawCursorRing); + canvas.restoreToCount(saveCount); + + // Now draw the shadow. + if (mTitleBar != null) { + int y = mScrollY + getVisibleTitleHeight(); + int height = (int) (5f * getContext().getResources() + .getDisplayMetrics().density); + mTitleShadow.setBounds(mScrollX, y, mScrollX + getWidth(), + y + height); + mTitleShadow.draw(canvas); } - int sc = canvas.save(); - if (mTouchMode >= FIRST_SCROLL_ZOOM && mTouchMode <= LAST_SCROLL_ZOOM) { - scrollZoomDraw(canvas); - } else { - nativeRecomputeFocus(); - // Update the buttons in the picture, so when we draw the picture - // to the screen, they are in the correct state. - // Tell the native side if user is a) touching the screen, - // b) pressing the trackball down, or c) pressing the enter key - // If the focus is a button, we need to draw it in the pressed - // state. - // If mNativeClass is 0, we should not reach here, so we do not - // need to check it again. - nativeRecordButtons(hasFocus() && hasWindowFocus(), - mTouchMode == TOUCH_SHORTPRESS_START_MODE - || mTrackballDown || mGotEnterDown, false); - drawCoreAndFocusRing(canvas, mBackgroundColor, mDrawFocusRing); - } - canvas.restoreToCount(sc); - if (AUTO_REDRAW_HACK && mAutoRedraw) { invalidate(); } @@ -2443,15 +2810,36 @@ public class WebView extends AbsoluteLayout @Override public boolean performLongClick() { + if (mNativeClass != 0 && nativeCursorIsTextInput()) { + // Send the click so that the textfield is in focus + // FIXME: When we start respecting changes to the native textfield's + // selection, need to make sure that this does not change it. + mWebViewCore.sendMessage(EventHub.CLICK, nativeCursorFramePointer(), + nativeCursorNodePointer()); + rebuildWebTextView(); + } if (inEditingMode()) { - return mTextEntry.performLongClick(); + return mWebTextView.performLongClick(); } else { return super.performLongClick(); } } - private void drawCoreAndFocusRing(Canvas canvas, int color, - boolean drawFocus) { + boolean inAnimateZoom() { + return mZoomScale != 0; + } + + /** + * Need to adjust the WebTextView after a change in zoom, since mActualScale + * has changed. This is especially important for password fields, which are + * drawn by the WebTextView, since it conveys more information than what + * webkit draws. Thus we need to reposition it to show in the correct + * place. + */ + private boolean mNeedToAdjustWebTextView; + + private void drawCoreAndCursorRing(Canvas canvas, int color, + boolean drawCursorRing) { if (mDrawHistory) { canvas.scale(mActualScale, mActualScale); canvas.drawPicture(mHistoryPicture); @@ -2459,41 +2847,79 @@ public class WebView extends AbsoluteLayout } boolean animateZoom = mZoomScale != 0; - boolean animateScroll = !mScroller.isFinished() + boolean animateScroll = !mScroller.isFinished() || mVelocityTracker != null; if (animateZoom) { float zoomScale; int interval = (int) (SystemClock.uptimeMillis() - mZoomStart); if (interval < ZOOM_ANIMATION_LENGTH) { float ratio = (float) interval / ZOOM_ANIMATION_LENGTH; - zoomScale = 1.0f / (mInvInitialZoomScale + zoomScale = 1.0f / (mInvInitialZoomScale + (mInvFinalZoomScale - mInvInitialZoomScale) * ratio); invalidate(); } else { zoomScale = mZoomScale; // set mZoomScale to be 0 as we have done animation mZoomScale = 0; + // call invalidate() again to draw with the final filters + invalidate(); + if (mNeedToAdjustWebTextView) { + mNeedToAdjustWebTextView = false; + Rect contentBounds = nativeFocusCandidateNodeBounds(); + Rect vBox = contentToViewRect(contentBounds); + Rect visibleRect = new Rect(); + calcOurVisibleRect(visibleRect); + if (visibleRect.contains(vBox)) { + // As a result of the zoom, the textfield is now on + // screen. Place the WebTextView in its new place, + // accounting for our new scroll/zoom values. + mWebTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, + contentToViewDimension( + nativeFocusCandidateTextSize())); + mWebTextView.setRect(vBox.left, vBox.top, vBox.width(), + vBox.height()); + // If it is a password field, start drawing the + // WebTextView once again. + if (nativeFocusCandidateIsPassword()) { + mWebTextView.setInPassword(true); + } + } else { + // The textfield is now off screen. The user probably + // was not zooming to see the textfield better. Remove + // the WebTextView. If the user types a key, and the + // textfield is still in focus, we will reconstruct + // the WebTextView and scroll it back on screen. + mWebTextView.remove(); + } + } } - float scale = (mActualScale - zoomScale) * mInvActualScale; - float tx = scale * (mZoomCenterX + mScrollX); - float ty = scale * (mZoomCenterY + mScrollY); - - // this block pins the translate to "legal" bounds. This makes the - // animation a bit non-obvious, but it means we won't pop when the - // "real" zoom takes effect - if (true) { - // canvas.translate(mScrollX, mScrollY); - tx -= mScrollX; - ty -= mScrollY; - tx = -pinLoc(-Math.round(tx), getViewWidth(), Math - .round(mContentWidth * zoomScale)); - ty = -pinLoc(-Math.round(ty), getViewHeight(), Math - .round(mContentHeight * zoomScale)); - tx += mScrollX; - ty += mScrollY; - } + // calculate the intermediate scroll position. As we need to use + // zoomScale, we can't use pinLocX/Y directly. Copy the logic here. + float scale = zoomScale * mInvInitialZoomScale; + int tx = Math.round(scale * (mInitialScrollX + mZoomCenterX) + - mZoomCenterX); + tx = -pinLoc(tx, getViewWidth(), Math.round(mContentWidth + * zoomScale)) + mScrollX; + int titleHeight = getTitleHeight(); + int ty = Math.round(scale + * (mInitialScrollY + mZoomCenterY - titleHeight) + - (mZoomCenterY - titleHeight)); + ty = -(ty <= titleHeight ? Math.max(ty, 0) : pinLoc(ty + - titleHeight, getViewHeight(), Math.round(mContentHeight + * zoomScale)) + titleHeight) + mScrollY; canvas.translate(tx, ty); canvas.scale(zoomScale, zoomScale); + if (inEditingMode() && !mNeedToAdjustWebTextView + && mZoomScale != 0) { + // The WebTextView is up. Keep track of this so we can adjust + // its size and placement when we finish zooming + mNeedToAdjustWebTextView = true; + // If it is in password mode, turn it off so it does not draw + // misplaced. + if (nativeFocusCandidateIsPassword()) { + mWebTextView.setInPassword(false); + } + } } else { canvas.scale(mActualScale, mActualScale); } @@ -2502,14 +2928,14 @@ public class WebView extends AbsoluteLayout animateScroll); if (mNativeClass == 0) return; - if (mShiftIsPressed) { + if (mShiftIsPressed && !animateZoom) { if (mTouchSelection) { nativeDrawSelectionRegion(canvas); } else { - nativeDrawSelection(canvas, mSelectX, mSelectY, - mExtendSelection); + nativeDrawSelection(canvas, mInvActualScale, getTitleHeight(), + mSelectX, mSelectY, mExtendSelection); } - } else if (drawFocus) { + } else if (drawCursorRing) { if (mTouchMode == TOUCH_SHORTPRESS_START_MODE) { mTouchMode = TOUCH_SHORTPRESS_MODE; HitTestResult hitTest = getHitTestResult(); @@ -2520,7 +2946,7 @@ public class WebView extends AbsoluteLayout LONG_PRESS_TIMEOUT); } } - nativeDrawFocusRing(canvas); + nativeDrawCursorRing(canvas); } // When the FindDialog is up, only draw the matches if we are not in // the process of scrolling them into view. @@ -2529,363 +2955,6 @@ public class WebView extends AbsoluteLayout } } - private native void nativeDrawMatches(Canvas canvas); - - private float scrollZoomGridScale(float invScale) { - float griddedInvScale = (int) (invScale * SCROLL_ZOOM_GRID) - / (float) SCROLL_ZOOM_GRID; - return 1.0f / griddedInvScale; - } - - private float scrollZoomX(float scale) { - int width = getViewWidth(); - float maxScrollZoomX = mContentWidth * scale - width; - int maxX = mContentWidth - width; - return -(maxScrollZoomX > 0 ? mZoomScrollX * maxScrollZoomX / maxX - : maxScrollZoomX / 2); - } - - private float scrollZoomY(float scale) { - int height = getViewHeight(); - float maxScrollZoomY = mContentHeight * scale - height; - int maxY = mContentHeight - height; - return -(maxScrollZoomY > 0 ? mZoomScrollY * maxScrollZoomY / maxY - : maxScrollZoomY / 2); - } - - private void drawMagnifyFrame(Canvas canvas, Rect frame, Paint paint) { - final float ADORNMENT_LEN = 16.0f; - float width = frame.width(); - float height = frame.height(); - Path path = new Path(); - path.moveTo(-ADORNMENT_LEN, -ADORNMENT_LEN); - path.lineTo(0, 0); - path.lineTo(width, 0); - path.lineTo(width + ADORNMENT_LEN, -ADORNMENT_LEN); - path.moveTo(-ADORNMENT_LEN, height + ADORNMENT_LEN); - path.lineTo(0, height); - path.lineTo(width, height); - path.lineTo(width + ADORNMENT_LEN, height + ADORNMENT_LEN); - path.moveTo(0, 0); - path.lineTo(0, height); - path.moveTo(width, 0); - path.lineTo(width, height); - path.offset(frame.left, frame.top); - canvas.drawPath(path, paint); - } - - // Returns frame surrounding magified portion of screen while - // scroll-zoom is enabled. The frame is also used to center the - // zoom-in zoom-out points at the start and end of the animation. - private Rect scrollZoomFrame(int width, int height, float halfScale) { - Rect scrollFrame = new Rect(); - scrollFrame.set(mZoomScrollX, mZoomScrollY, - mZoomScrollX + width, mZoomScrollY + height); - if (mContentWidth * mZoomScrollLimit < width) { - float scale = zoomFrameScaleX(width, halfScale, 1.0f); - float offsetX = (width * scale - width) * 0.5f; - scrollFrame.left -= offsetX; - scrollFrame.right += offsetX; - } - if (mContentHeight * mZoomScrollLimit < height) { - float scale = zoomFrameScaleY(height, halfScale, 1.0f); - float offsetY = (height * scale - height) * 0.5f; - scrollFrame.top -= offsetY; - scrollFrame.bottom += offsetY; - } - return scrollFrame; - } - - private float zoomFrameScaleX(int width, float halfScale, float noScale) { - // mContentWidth > width > mContentWidth * mZoomScrollLimit - if (mContentWidth <= width) { - return halfScale; - } - float part = (width - mContentWidth * mZoomScrollLimit) - / (width * (1 - mZoomScrollLimit)); - return halfScale * part + noScale * (1.0f - part); - } - - private float zoomFrameScaleY(int height, float halfScale, float noScale) { - if (mContentHeight <= height) { - return halfScale; - } - float part = (height - mContentHeight * mZoomScrollLimit) - / (height * (1 - mZoomScrollLimit)); - return halfScale * part + noScale * (1.0f - part); - } - - private float scrollZoomMagScale(float invScale) { - return (invScale * 2 + mInvActualScale) / 3; - } - - private void scrollZoomDraw(Canvas canvas) { - float invScale = mZoomScrollInvLimit; - int elapsed = 0; - if (mTouchMode != SCROLL_ZOOM_OUT) { - elapsed = (int) Math.min(System.currentTimeMillis() - - mZoomScrollStart, SCROLL_ZOOM_DURATION); - float transitionScale = (mZoomScrollInvLimit - mInvActualScale) - * elapsed / SCROLL_ZOOM_DURATION; - if (mTouchMode == SCROLL_ZOOM_ANIMATION_OUT) { - invScale = mInvActualScale + transitionScale; - } else { /* if (mTouchMode == SCROLL_ZOOM_ANIMATION_IN) */ - invScale = mZoomScrollInvLimit - transitionScale; - } - } - float scale = scrollZoomGridScale(invScale); - invScale = 1.0f / scale; - int width = getViewWidth(); - int height = getViewHeight(); - float halfScale = scrollZoomMagScale(invScale); - Rect scrollFrame = scrollZoomFrame(width, height, halfScale); - if (elapsed == SCROLL_ZOOM_DURATION) { - if (mTouchMode == SCROLL_ZOOM_ANIMATION_IN) { - setHorizontalScrollBarEnabled(true); - setVerticalScrollBarEnabled(true); - updateTextEntry(); - scrollTo((int) (scrollFrame.centerX() * mActualScale) - - (width >> 1), (int) (scrollFrame.centerY() - * mActualScale) - (height >> 1)); - mTouchMode = TOUCH_DONE_MODE; - } else { - mTouchMode = SCROLL_ZOOM_OUT; - } - } - float newX = scrollZoomX(scale); - float newY = scrollZoomY(scale); - if (LOGV_ENABLED) { - Log.v(LOGTAG, "scrollZoomDraw scale=" + scale + " + (" + newX - + ", " + newY + ") mZoomScroll=(" + mZoomScrollX + ", " - + mZoomScrollY + ")" + " invScale=" + invScale + " scale=" - + scale); - } - canvas.translate(newX, newY); - canvas.scale(scale, scale); - boolean animating = mTouchMode != SCROLL_ZOOM_OUT; - if (mDrawHistory) { - int sc = canvas.save(Canvas.CLIP_SAVE_FLAG); - Rect clip = new Rect(0, 0, mHistoryPicture.getWidth(), - mHistoryPicture.getHeight()); - canvas.clipRect(clip, Region.Op.DIFFERENCE); - canvas.drawColor(mBackgroundColor); - canvas.restoreToCount(sc); - canvas.drawPicture(mHistoryPicture); - } else { - mWebViewCore.drawContentPicture(canvas, mBackgroundColor, - animating, true); - } - if (mTouchMode == TOUCH_DONE_MODE) { - return; - } - Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); - paint.setStyle(Paint.Style.STROKE); - paint.setStrokeWidth(30.0f); - paint.setARGB(0x50, 0, 0, 0); - int maxX = mContentWidth - width; - int maxY = mContentHeight - height; - if (true) { // experiment: draw hint to place finger off magnify area - drawMagnifyFrame(canvas, scrollFrame, paint); - } else { - canvas.drawRect(scrollFrame, paint); - } - int sc = canvas.save(); - canvas.clipRect(scrollFrame); - float halfX = (float) mZoomScrollX / maxX; - if (mContentWidth * mZoomScrollLimit < width) { - halfX = zoomFrameScaleX(width, 0.5f, halfX); - } - float halfY = (float) mZoomScrollY / maxY; - if (mContentHeight * mZoomScrollLimit < height) { - halfY = zoomFrameScaleY(height, 0.5f, halfY); - } - canvas.scale(halfScale, halfScale, mZoomScrollX + width * halfX - , mZoomScrollY + height * halfY); - if (LOGV_ENABLED) { - Log.v(LOGTAG, "scrollZoomDraw halfScale=" + halfScale + " w/h=(" - + width + ", " + height + ") half=(" + halfX + ", " - + halfY + ")"); - } - if (mDrawHistory) { - canvas.drawPicture(mHistoryPicture); - } else { - mWebViewCore.drawContentPicture(canvas, mBackgroundColor, - animating, false); - } - canvas.restoreToCount(sc); - if (mTouchMode != SCROLL_ZOOM_OUT) { - invalidate(); - } - } - - private void zoomScrollTap(float x, float y) { - float scale = scrollZoomGridScale(mZoomScrollInvLimit); - float left = scrollZoomX(scale); - float top = scrollZoomY(scale); - int width = getViewWidth(); - int height = getViewHeight(); - x -= width * scale / 2; - y -= height * scale / 2; - mZoomScrollX = Math.min(mContentWidth - width - , Math.max(0, (int) ((x - left) / scale))); - mZoomScrollY = Math.min(mContentHeight - height - , Math.max(0, (int) ((y - top) / scale))); - if (LOGV_ENABLED) { - Log.v(LOGTAG, "zoomScrollTap scale=" + scale + " + (" + left - + ", " + top + ") mZoomScroll=(" + mZoomScrollX + ", " - + mZoomScrollY + ")" + " x=" + x + " y=" + y); - } - } - - private boolean canZoomScrollOut() { - if (mContentWidth == 0 || mContentHeight == 0) { - return false; - } - int width = getViewWidth(); - int height = getViewHeight(); - float x = (float) width / (float) mContentWidth; - float y = (float) height / (float) mContentHeight; - mZoomScrollLimit = Math.max(DEFAULT_MIN_ZOOM_SCALE, Math.min(x, y)); - mZoomScrollInvLimit = 1.0f / mZoomScrollLimit; - if (LOGV_ENABLED) { - Log.v(LOGTAG, "canZoomScrollOut" - + " mInvActualScale=" + mInvActualScale - + " mZoomScrollLimit=" + mZoomScrollLimit - + " mZoomScrollInvLimit=" + mZoomScrollInvLimit - + " mContentWidth=" + mContentWidth - + " mContentHeight=" + mContentHeight - ); - } - // don't zoom out unless magnify area is at least half as wide - // or tall as content - float limit = mZoomScrollLimit * 2; - return mContentWidth >= width * limit - || mContentHeight >= height * limit; - } - - private void startZoomScrollOut() { - setHorizontalScrollBarEnabled(false); - setVerticalScrollBarEnabled(false); - if (getSettings().getBuiltInZoomControls()) { - if (mZoomButtonsController.isVisible()) { - mZoomButtonsController.setVisible(false); - } - } else { - if (mZoomControlRunnable != null) { - mPrivateHandler.removeCallbacks(mZoomControlRunnable); - } - if (mZoomControls != null) { - mZoomControls.hide(); - } - } - int width = getViewWidth(); - int height = getViewHeight(); - int halfW = width >> 1; - mLastTouchX = halfW; - int halfH = height >> 1; - mLastTouchY = halfH; - mScroller.abortAnimation(); - mZoomScrollStart = System.currentTimeMillis(); - Rect zoomFrame = scrollZoomFrame(width, height - , scrollZoomMagScale(mZoomScrollInvLimit)); - mZoomScrollX = Math.max(0, (int) ((mScrollX + halfW) * mInvActualScale) - - (zoomFrame.width() >> 1)); - mZoomScrollY = Math.max(0, (int) ((mScrollY + halfH) * mInvActualScale) - - (zoomFrame.height() >> 1)); - scrollTo(0, 0); // triggers inval, starts animation - clearTextEntry(); - if (LOGV_ENABLED) { - Log.v(LOGTAG, "startZoomScrollOut mZoomScroll=(" - + mZoomScrollX + ", " + mZoomScrollY +")"); - } - } - - private void zoomScrollOut() { - if (canZoomScrollOut() == false) { - mTouchMode = TOUCH_DONE_MODE; - return; - } - startZoomScrollOut(); - mTouchMode = SCROLL_ZOOM_ANIMATION_OUT; - invalidate(); - } - - private void moveZoomScrollWindow(float x, float y) { - if (Math.abs(x - mLastZoomScrollRawX) < 1.5f - && Math.abs(y - mLastZoomScrollRawY) < 1.5f) { - return; - } - mLastZoomScrollRawX = x; - mLastZoomScrollRawY = y; - int oldX = mZoomScrollX; - int oldY = mZoomScrollY; - int width = getViewWidth(); - int height = getViewHeight(); - int maxZoomX = mContentWidth - width; - if (maxZoomX > 0) { - int maxScreenX = width - (int) Math.ceil(width - * mZoomScrollLimit) - SCROLL_ZOOM_FINGER_BUFFER; - if (LOGV_ENABLED) { - Log.v(LOGTAG, "moveZoomScrollWindow-X" - + " maxScreenX=" + maxScreenX + " width=" + width - + " mZoomScrollLimit=" + mZoomScrollLimit + " x=" + x); - } - x += maxScreenX * mLastScrollX / maxZoomX - mLastTouchX; - x *= Math.max(maxZoomX / maxScreenX, mZoomScrollInvLimit); - mZoomScrollX = Math.max(0, Math.min(maxZoomX, (int) x)); - } - int maxZoomY = mContentHeight - height; - if (maxZoomY > 0) { - int maxScreenY = height - (int) Math.ceil(height - * mZoomScrollLimit) - SCROLL_ZOOM_FINGER_BUFFER; - if (LOGV_ENABLED) { - Log.v(LOGTAG, "moveZoomScrollWindow-Y" - + " maxScreenY=" + maxScreenY + " height=" + height - + " mZoomScrollLimit=" + mZoomScrollLimit + " y=" + y); - } - y += maxScreenY * mLastScrollY / maxZoomY - mLastTouchY; - y *= Math.max(maxZoomY / maxScreenY, mZoomScrollInvLimit); - mZoomScrollY = Math.max(0, Math.min(maxZoomY, (int) y)); - } - if (oldX != mZoomScrollX || oldY != mZoomScrollY) { - invalidate(); - } - if (LOGV_ENABLED) { - Log.v(LOGTAG, "moveZoomScrollWindow" - + " scrollTo=(" + mZoomScrollX + ", " + mZoomScrollY + ")" - + " mLastTouch=(" + mLastTouchX + ", " + mLastTouchY + ")" - + " maxZoom=(" + maxZoomX + ", " + maxZoomY + ")" - + " last=("+mLastScrollX+", "+mLastScrollY+")" - + " x=" + x + " y=" + y); - } - } - - private void setZoomScrollIn() { - mZoomScrollStart = System.currentTimeMillis(); - } - - private float mZoomScrollLimit; - private float mZoomScrollInvLimit; - private int mLastScrollX; - private int mLastScrollY; - private long mZoomScrollStart; - private int mZoomScrollX; - private int mZoomScrollY; - private float mLastZoomScrollRawX = -1000.0f; - private float mLastZoomScrollRawY = -1000.0f; - // The zoomed scale varies from 1.0 to DEFAULT_MIN_ZOOM_SCALE == 0.25. - // The zoom animation duration SCROLL_ZOOM_DURATION == 0.5. - // Two pressures compete for gridding; a high frame rate (e.g. 20 fps) - // and minimizing font cache allocations (fewer frames is better). - // A SCROLL_ZOOM_GRID of 6 permits about 20 zoom levels over 0.5 seconds: - // the inverse of: 1.0, 1.16, 1.33, 1.5, 1.67, 1.84, 2.0, etc. to 4.0 - private static final int SCROLL_ZOOM_GRID = 6; - private static final int SCROLL_ZOOM_DURATION = 500; - // Make it easier to get to the bottom of a document by reserving a 32 - // pixel buffer, for when the starting drag is a bit below the bottom of - // the magnify frame. - private static final int SCROLL_ZOOM_FINGER_BUFFER = 32; - // draw history private boolean mDrawHistory = false; private Picture mHistoryPicture = null; @@ -2900,7 +2969,7 @@ public class WebView extends AbsoluteLayout // Should only be called in UI thread void switchOutDrawHistory() { if (null == mWebViewCore) return; // CallbackProxy may trigger this - if (mDrawHistory) { + if (mDrawHistory && mWebViewCore.pictureReady()) { mDrawHistory = false; invalidate(); int oldScrollX = mScrollX; @@ -2916,72 +2985,29 @@ public class WebView extends AbsoluteLayout } } - /** - * Class representing the node which is focused. - */ - private class FocusNode { - public FocusNode() { - mBounds = new Rect(); - } - // Only to be called by JNI - private void setAll(boolean isTextField, boolean isTextArea, boolean - isPassword, boolean isAnchor, boolean isRtlText, int maxLength, - int textSize, int boundsX, int boundsY, int boundsRight, int - boundsBottom, int nodePointer, int framePointer, String text, - String name, int rootTextGeneration) { - mIsTextField = isTextField; - mIsTextArea = isTextArea; - mIsPassword = isPassword; - mIsAnchor = isAnchor; - mIsRtlText = isRtlText; - - mMaxLength = maxLength; - mTextSize = textSize; - - mBounds.set(boundsX, boundsY, boundsRight, boundsBottom); - - - mNodePointer = nodePointer; - mFramePointer = framePointer; - mText = text; - mName = name; - mRootTextGeneration = rootTextGeneration; - } - public boolean mIsTextField; - public boolean mIsTextArea; - public boolean mIsPassword; - public boolean mIsAnchor; - public boolean mIsRtlText; - - public int mSelectionStart; - public int mSelectionEnd; - public int mMaxLength; - public int mTextSize; - - public Rect mBounds; - - public int mNodePointer; - public int mFramePointer; - public String mText; - public String mName; - public int mRootTextGeneration; - } - - // Warning: ONLY use mFocusNode AFTER calling nativeUpdateFocusNode(), - // and ONLY if it returns true; - private FocusNode mFocusNode = new FocusNode(); - + WebViewCore.CursorData cursorData() { + WebViewCore.CursorData result = new WebViewCore.CursorData(); + result.mMoveGeneration = nativeMoveGeneration(); + result.mFrame = nativeCursorFramePointer(); + Point position = nativeCursorPosition(); + result.mX = position.x; + result.mY = position.y; + return result; + } + /** * Delete text from start to end in the focused textfield. If there is no - * focus, or if start == end, silently fail. If start and end are out of + * focus, or if start == end, silently fail. If start and end are out of * order, swap them. * @param start Beginning of selection to delete. * @param end End of selection to delete. */ /* package */ void deleteSelection(int start, int end) { mTextGeneration++; - mWebViewCore.sendMessage(EventHub.DELETE_SELECTION, start, end, - new WebViewCore.FocusData(mFocusData)); + WebViewCore.TextSelectionData data + = new WebViewCore.TextSelectionData(start, end); + mWebViewCore.sendMessage(EventHub.DELETE_SELECTION, mTextGeneration, 0, + data); } /** @@ -2991,119 +3017,128 @@ public class WebView extends AbsoluteLayout * @param end End of selection. */ /* package */ void setSelection(int start, int end) { - mWebViewCore.sendMessage(EventHub.SET_SELECTION, start, end, - new WebViewCore.FocusData(mFocusData)); + mWebViewCore.sendMessage(EventHub.SET_SELECTION, start, end); } // Called by JNI when a touch event puts a textfield into focus. - private void displaySoftKeyboard() { + private void displaySoftKeyboard(boolean isTextView) { InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - imm.showSoftInput(mTextEntry, 0); - mTextEntry.enableScrollOnScreen(true); - // Now we need to fake a touch event to place the cursor where the - // user touched. - AbsoluteLayout.LayoutParams lp = (AbsoluteLayout.LayoutParams) - mTextEntry.getLayoutParams(); - if (lp != null) { - // Take the last touch and adjust for the location of the - // TextDialog. - float x = mLastTouchX + (float) (mScrollX - lp.x); - float y = mLastTouchY + (float) (mScrollY - lp.y); - mTextEntry.fakeTouchEvent(x, y); - } - } - - private void updateTextEntry() { - if (mTextEntry == null) { - mTextEntry = new TextDialog(mContext, WebView.this); - // Initialize our generation number. - mTextGeneration = 0; + + if (isTextView) { + if (mWebTextView == null) return; + + imm.showSoftInput(mWebTextView, 0); + if (mInZoomOverview) { + // if in zoom overview mode, call doDoubleTap() to bring it back + // to normal mode so that user can enter text. + doDoubleTap(); + } + } + else { // used by plugins + imm.showSoftInput(this, 0); } - // If we do not have focus, do nothing until we gain focus. - if (!hasFocus() && !mTextEntry.hasFocus() - || (mTouchMode >= FIRST_SCROLL_ZOOM - && mTouchMode <= LAST_SCROLL_ZOOM)) { - mNeedsUpdateTextEntry = true; + } + + // Called by WebKit to instruct the UI to hide the keyboard + private void hideSoftKeyboard() { + InputMethodManager imm = (InputMethodManager) + getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + + imm.hideSoftInputFromWindow(this.getWindowToken(), 0); + } + + /* + * This method checks the current focus and cursor and potentially rebuilds + * mWebTextView to have the appropriate properties, such as password, + * multiline, and what text it contains. It also removes it if necessary. + */ + /* package */ void rebuildWebTextView() { + // If the WebView does not have focus, do nothing until it gains focus. + if (!hasFocus() && (null == mWebTextView || !mWebTextView.hasFocus())) { return; } boolean alreadyThere = inEditingMode(); - if (0 == mNativeClass || !nativeUpdateFocusNode()) { + // inEditingMode can only return true if mWebTextView is non-null, + // so we can safely call remove() if (alreadyThere) + if (0 == mNativeClass || !nativeFocusCandidateIsTextInput()) { if (alreadyThere) { - mTextEntry.remove(); + mWebTextView.remove(); } return; } - FocusNode node = mFocusNode; - if (!node.mIsTextField && !node.mIsTextArea) { - if (alreadyThere) { - mTextEntry.remove(); - } - return; + // At this point, we know we have found an input field, so go ahead + // and create the WebTextView if necessary. + if (mWebTextView == null) { + mWebTextView = new WebTextView(mContext, WebView.this); + // Initialize our generation number. + mTextGeneration = 0; } - mTextEntry.setTextSize(contentToView(node.mTextSize)); - Rect visibleRect = sendOurVisibleRect(); + mWebTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, + contentToViewDimension(nativeFocusCandidateTextSize())); + Rect visibleRect = new Rect(); + calcOurContentVisibleRect(visibleRect); // Note that sendOurVisibleRect calls viewToContent, so the coordinates // should be in content coordinates. - if (!Rect.intersects(node.mBounds, visibleRect)) { - // Node is not on screen, so do not bother. - return; + Rect bounds = nativeFocusCandidateNodeBounds(); + if (!Rect.intersects(bounds, visibleRect)) { + mWebTextView.bringIntoView(); } - int x = node.mBounds.left; - int y = node.mBounds.top; - int width = node.mBounds.width(); - int height = node.mBounds.height(); - if (alreadyThere && mTextEntry.isSameTextField(node.mNodePointer)) { + String text = nativeFocusCandidateText(); + int nodePointer = nativeFocusCandidatePointer(); + if (alreadyThere && mWebTextView.isSameTextField(nodePointer)) { // It is possible that we have the same textfield, but it has moved, // i.e. In the case of opening/closing the screen. // In that case, we need to set the dimensions, but not the other // aspects. // We also need to restore the selection, which gets wrecked by // calling setTextEntryRect. - Spannable spannable = (Spannable) mTextEntry.getText(); + Spannable spannable = (Spannable) mWebTextView.getText(); int start = Selection.getSelectionStart(spannable); int end = Selection.getSelectionEnd(spannable); - setTextEntryRect(x, y, width, height); // If the text has been changed by webkit, update it. However, if // there has been more UI text input, ignore it. We will receive // another update when that text is recognized. - if (node.mText != null && !node.mText.equals(spannable.toString()) - && node.mRootTextGeneration == mTextGeneration) { - mTextEntry.setTextAndKeepSelection(node.mText); + if (text != null && !text.equals(spannable.toString()) + && nativeTextGeneration() == mTextGeneration) { + mWebTextView.setTextAndKeepSelection(text); } else { Selection.setSelection(spannable, start, end); } } else { - String text = node.mText; - setTextEntryRect(x, y, width, height); - mTextEntry.setGravity(node.mIsRtlText ? Gravity.RIGHT : - Gravity.NO_GRAVITY); + Rect vBox = contentToViewRect(bounds); + mWebTextView.setRect(vBox.left, vBox.top, vBox.width(), + vBox.height()); + mWebTextView.setGravity(nativeFocusCandidateIsRtlText() ? + Gravity.RIGHT : Gravity.NO_GRAVITY); // this needs to be called before update adapter thread starts to - // ensure the mTextEntry has the same node pointer - mTextEntry.setNodePointer(node.mNodePointer); + // ensure the mWebTextView has the same node pointer + mWebTextView.setNodePointer(nodePointer); int maxLength = -1; - if (node.mIsTextField) { - maxLength = node.mMaxLength; + boolean isTextField = nativeFocusCandidateIsTextField(); + if (isTextField) { + maxLength = nativeFocusCandidateMaxLength(); + String name = nativeFocusCandidateName(); if (mWebViewCore.getSettings().getSaveFormData() - && node.mName != null) { - HashMap data = new HashMap(); - data.put("text", node.mText); + && name != null) { Message update = mPrivateHandler.obtainMessage( - UPDATE_TEXT_ENTRY_ADAPTER, node.mNodePointer, 0, - data); - UpdateTextEntryAdapter updater = new UpdateTextEntryAdapter( - node.mName, getUrl(), update); + REQUEST_FORM_DATA, nodePointer); + RequestFormData updater = new RequestFormData(name, + getUrl(), update); Thread t = new Thread(updater); t.start(); } } - mTextEntry.setMaxLength(maxLength); + mWebTextView.setMaxLength(maxLength); AutoCompleteAdapter adapter = null; - mTextEntry.setAdapterCustom(adapter); - mTextEntry.setSingleLine(node.mIsTextField); - mTextEntry.setInPassword(node.mIsPassword); + mWebTextView.setAdapterCustom(adapter); + mWebTextView.setSingleLine(isTextField); + mWebTextView.setInPassword(nativeFocusCandidateIsPassword()); if (null == text) { - mTextEntry.setText("", 0, 0); + mWebTextView.setText("", 0, 0); + if (DebugFlags.WEB_VIEW) { + Log.v(LOGTAG, "rebuildWebTextView null == text"); + } } else { // Change to true to enable the old style behavior, where // entering a textfield/textarea always set the selection to the @@ -3114,24 +3149,35 @@ public class WebView extends AbsoluteLayout // textarea. Testing out a new behavior, where textfields set // selection at the end, and textareas at the beginning. if (false) { - mTextEntry.setText(text, 0, text.length()); - } else if (node.mIsTextField) { + mWebTextView.setText(text, 0, text.length()); + } else if (isTextField) { int length = text.length(); - mTextEntry.setText(text, length, length); + mWebTextView.setText(text, length, length); + if (DebugFlags.WEB_VIEW) { + Log.v(LOGTAG, "rebuildWebTextView length=" + length); + } } else { - mTextEntry.setText(text, 0, 0); + mWebTextView.setText(text, 0, 0); + if (DebugFlags.WEB_VIEW) { + Log.v(LOGTAG, "rebuildWebTextView !isTextField"); + } } } - mTextEntry.requestFocus(); + mWebTextView.requestFocus(); } } - private class UpdateTextEntryAdapter implements Runnable { + /* + * This class requests an Adapter for the WebTextView which shows past + * entries stored in the database. It is a Runnable so that it can be done + * in its own thread, without slowing down the UI. + */ + private class RequestFormData implements Runnable { private String mName; private String mUrl; private Message mUpdateMessage; - public UpdateTextEntryAdapter(String name, String url, Message msg) { + public RequestFormData(String name, String url, Message msg) { mName = name; mUrl = url; mUpdateMessage = msg; @@ -3142,29 +3188,21 @@ public class WebView extends AbsoluteLayout if (pastEntries.size() > 0) { AutoCompleteAdapter adapter = new AutoCompleteAdapter(mContext, pastEntries); - ((HashMap) mUpdateMessage.obj).put("adapter", adapter); + mUpdateMessage.obj = adapter; mUpdateMessage.sendToTarget(); } } } - private void setTextEntryRect(int x, int y, int width, int height) { - x = contentToView(x); - y = contentToView(y); - width = contentToView(width); - height = contentToView(height); - mTextEntry.setRect(x, y, width, height); - } - - // This is used to determine long press with the enter key, or - // a center key. Does not affect long press with the trackball/touch. - private boolean mGotEnterDown = false; + // This is used to determine long press with the center key. Does not + // affect long press with the trackball/touch. + private boolean mGotCenterDown = false; @Override public boolean onKeyDown(int keyCode, KeyEvent event) { - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "keyDown at " + System.currentTimeMillis() - + ", " + event); + + ", " + event + ", unicode=" + event.getUnicodeChar()); } if (mNativeClass == 0) { @@ -3182,37 +3220,33 @@ public class WebView extends AbsoluteLayout // Bubble up the key event if // 1. it is a system key; or - // 2. the host application wants to handle it; or - // 3. webview is in scroll-zoom state; + // 2. the host application wants to handle it; if (event.isSystem() - || mCallbackProxy.uiOverrideKeyEvent(event) - || (mTouchMode >= FIRST_SCROLL_ZOOM && mTouchMode <= LAST_SCROLL_ZOOM)) { + || mCallbackProxy.uiOverrideKeyEvent(event)) { return false; } - if (mShiftIsPressed == false && nativeFocusNodeWantsKeyEvents() == false - && (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT + if (mShiftIsPressed == false && nativeCursorWantsKeyEvents() == false + && (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT)) { mExtendSelection = false; mShiftIsPressed = true; - if (nativeUpdateFocusNode()) { - FocusNode node = mFocusNode; - mSelectX = contentToView(node.mBounds.left); - mSelectY = contentToView(node.mBounds.top); + if (nativeHasCursorNode()) { + Rect rect = nativeCursorNodeBounds(); + mSelectX = contentToViewX(rect.left); + mSelectY = contentToViewY(rect.top); } else { mSelectX = mScrollX + (int) mLastTouchX; mSelectY = mScrollY + (int) mLastTouchY; } - int contentX = viewToContent((int) mLastTouchX + mScrollX); - int contentY = viewToContent((int) mLastTouchY + mScrollY); - nativeClearFocus(contentX, contentY); + nativeHideCursor(); } if (keyCode >= KeyEvent.KEYCODE_DPAD_UP && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) { // always handle the navigation keys in the UI thread switchOutDrawHistory(); - if (navHandledKey(keyCode, 1, false, event.getEventTime())) { + if (navHandledKey(keyCode, 1, false, event.getEventTime(), false)) { playSoundEffect(keyCodeToSoundsEffect(keyCode)); return true; } @@ -3220,13 +3254,12 @@ public class WebView extends AbsoluteLayout return false; } - if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER - || keyCode == KeyEvent.KEYCODE_ENTER) { + if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { switchOutDrawHistory(); if (event.getRepeatCount() == 0) { - mGotEnterDown = true; + mGotCenterDown = true; mPrivateHandler.sendMessageDelayed(mPrivateHandler - .obtainMessage(LONG_PRESS_ENTER), LONG_PRESS_TIMEOUT); + .obtainMessage(LONG_PRESS_CENTER), LONG_PRESS_TIMEOUT); // Already checked mNativeClass, so we do not need to check it // again. nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true); @@ -3236,6 +3269,15 @@ public class WebView extends AbsoluteLayout return false; } + if (keyCode != KeyEvent.KEYCODE_SHIFT_LEFT + && keyCode != KeyEvent.KEYCODE_SHIFT_RIGHT) { + // turn off copy select if a shift-key combo is pressed + mExtendSelection = mShiftIsPressed = false; + if (mTouchMode == TOUCH_SELECT_MODE) { + mTouchMode = TOUCH_INIT_MODE; + } + } + if (getSettings().getNavDump()) { switch (keyCode) { case KeyEvent.KEYCODE_4: @@ -3264,8 +3306,30 @@ public class WebView extends AbsoluteLayout } } + if (nativeCursorIsPlugin()) { + nativeUpdatePluginReceivesEvents(); + invalidate(); + } else if (nativeCursorIsTextInput()) { + // This message will put the node in focus, for the DOM's notion + // of focus, and make the focuscontroller active + mWebViewCore.sendMessage(EventHub.CLICK, nativeCursorFramePointer(), + nativeCursorNodePointer()); + // This will bring up the WebTextView and put it in focus, for + // our view system's notion of focus + rebuildWebTextView(); + // Now we need to pass the event to it + return mWebTextView.onKeyDown(keyCode, event); + } else if (nativeHasFocusNode()) { + // In this case, the cursor is not on a text input, but the focus + // might be. Check it, and if so, hand over to the WebTextView. + rebuildWebTextView(); + if (inEditingMode()) { + return mWebTextView.onKeyDown(keyCode, event); + } + } + // TODO: should we pass all the keys to DOM or check the meta tag - if (nativeFocusNodeWantsKeyEvents() || true) { + if (nativeCursorWantsKeyEvents() || true) { // pass the key to DOM mWebViewCore.sendMessage(EventHub.KEY_DOWN, event); // return true as DOM handles the key @@ -3278,20 +3342,19 @@ public class WebView extends AbsoluteLayout @Override public boolean onKeyUp(int keyCode, KeyEvent event) { - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "keyUp at " + System.currentTimeMillis() - + ", " + event); + + ", " + event + ", unicode=" + event.getUnicodeChar()); } if (mNativeClass == 0) { return false; } - // special CALL handling when focus node's href is "tel:XXX" - if (keyCode == KeyEvent.KEYCODE_CALL && nativeUpdateFocusNode()) { - FocusNode node = mFocusNode; - String text = node.mText; - if (!node.mIsTextField && !node.mIsTextArea && text != null + // special CALL handling when cursor node's href is "tel:XXX" + if (keyCode == KeyEvent.KEYCODE_CALL && nativeHasCursorNode()) { + String text = nativeCursorText(); + if (!nativeCursorIsTextInput() && text != null && text.startsWith(SCHEME_TEL)) { Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(text)); getContext().startActivity(intent); @@ -3306,19 +3369,7 @@ public class WebView extends AbsoluteLayout return false; } - // special handling in scroll_zoom state - if (mTouchMode >= FIRST_SCROLL_ZOOM && mTouchMode <= LAST_SCROLL_ZOOM) { - if (KeyEvent.KEYCODE_DPAD_CENTER == keyCode - && mTouchMode != SCROLL_ZOOM_ANIMATION_IN) { - setZoomScrollIn(); - mTouchMode = SCROLL_ZOOM_ANIMATION_IN; - invalidate(); - return true; - } - return false; - } - - if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT + if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { if (commitCopy()) { return true; @@ -3332,55 +3383,42 @@ public class WebView extends AbsoluteLayout return false; } - if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER - || keyCode == KeyEvent.KEYCODE_ENTER) { + if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { // remove the long press message first - mPrivateHandler.removeMessages(LONG_PRESS_ENTER); - mGotEnterDown = false; + mPrivateHandler.removeMessages(LONG_PRESS_CENTER); + mGotCenterDown = false; - if (KeyEvent.KEYCODE_DPAD_CENTER == keyCode) { - if (mShiftIsPressed) { - return false; - } - if (getSettings().supportZoom()) { - if (mTouchMode == TOUCH_DOUBLECLICK_MODE) { - zoomScrollOut(); - } else { - if (LOGV_ENABLED) { - Log.v(LOGTAG, "TOUCH_DOUBLECLICK_MODE"); - } - mPrivateHandler.sendMessageDelayed(mPrivateHandler - .obtainMessage(SWITCH_TO_ENTER), TAP_TIMEOUT); - mTouchMode = TOUCH_DOUBLECLICK_MODE; - } - return true; - } + if (mShiftIsPressed) { + return false; } + // perform the single click Rect visibleRect = sendOurVisibleRect(); // Note that sendOurVisibleRect calls viewToContent, so the // coordinates should be in content coordinates. - if (nativeUpdateFocusNode()) { - if (Rect.intersects(mFocusNode.mBounds, visibleRect)) { - nativeSetFollowedLink(true); - mWebViewCore.sendMessage(EventHub.SET_FINAL_FOCUS, - EventHub.BLOCK_FOCUS_CHANGE_UNTIL_KEY_UP, 0, - new WebViewCore.FocusData(mFocusData)); - playSoundEffect(SoundEffectConstants.CLICK); - if (!mCallbackProxy.uiOverrideUrlLoading(mFocusNode.mText)) { - // use CLICK instead of KEY_DOWN/KEY_UP so that we can - // trigger mouse click events - mWebViewCore.sendMessage(EventHub.CLICK); - } - } - return true; + if (!nativeCursorIntersects(visibleRect)) { + return false; } - // Bubble up the key event as WebView doesn't handle it - return false; + nativeSetFollowedLink(true); + nativeUpdatePluginReceivesEvents(); + WebViewCore.CursorData data = cursorData(); + mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, data); + playSoundEffect(SoundEffectConstants.CLICK); + boolean isTextInput = nativeCursorIsTextInput(); + if (isTextInput || !mCallbackProxy.uiOverrideUrlLoading( + nativeCursorText())) { + mWebViewCore.sendMessage(EventHub.CLICK, data.mFrame, + nativeCursorNodePointer()); + } + if (isTextInput) { + rebuildWebTextView(); + displaySoftKeyboard(true); + } + return true; } // TODO: should we pass all the keys to DOM or check the meta tag - if (nativeFocusNodeWantsKeyEvents() || true) { + if (nativeCursorWantsKeyEvents() || true) { // pass the key to DOM mWebViewCore.sendMessage(EventHub.KEY_UP, event); // return true as DOM handles the key @@ -3390,16 +3428,15 @@ public class WebView extends AbsoluteLayout // Bubble up the key event as WebView doesn't handle it return false; } - + /** * @hide */ public void emulateShiftHeld() { + if (0 == mNativeClass) return; // client isn't initialized mExtendSelection = false; mShiftIsPressed = true; - int contentX = viewToContent((int) mLastTouchX + mScrollX); - int contentY = viewToContent((int) mLastTouchY + mScrollY); - nativeClearFocus(contentX, contentY); + nativeHideCursor(); } private boolean commitCopy() { @@ -3447,16 +3484,13 @@ public class WebView extends AbsoluteLayout // Clean up the zoom controller mZoomButtonsController.setVisible(false); } - + // Implementation for OnHierarchyChangeListener public void onChildViewAdded(View parent, View child) {} - + public void onChildViewRemoved(View p, View child) { if (child == this) { - if (inEditingMode()) { - clearTextEntry(); - mNeedsUpdateTextEntry = true; - } + clearTextEntry(); } } @@ -3469,26 +3503,25 @@ public class WebView extends AbsoluteLayout public void onGlobalFocusChanged(View oldFocus, View newFocus) { } - // To avoid drawing the focus ring, and remove the TextView when our window + // To avoid drawing the cursor ring, and remove the TextView when our window // loses focus. @Override public void onWindowFocusChanged(boolean hasWindowFocus) { if (hasWindowFocus) { if (hasFocus()) { // If our window regained focus, and we have focus, then begin - // drawing the focus ring, and restore the TextView if - // necessary. - mDrawFocusRing = true; - if (mNeedsUpdateTextEntry) { - updateTextEntry(); - } + // drawing the cursor ring + mDrawCursorRing = true; if (mNativeClass != 0) { nativeRecordButtons(true, false, true); + if (inEditingMode()) { + mWebViewCore.sendMessage(EventHub.SET_ACTIVE, 1, 0); + } } } else { // If our window gained focus, but we do not have it, do not - // draw the focus ring. - mDrawFocusRing = false; + // draw the cursor ring. + mDrawCursorRing = false; // We do not call nativeRecordButtons here because we assume // that when we lost focus, or window focus, it got called with // false for the first parameter @@ -3497,39 +3530,49 @@ public class WebView extends AbsoluteLayout if (getSettings().getBuiltInZoomControls() && !mZoomButtonsController.isVisible()) { /* * The zoom controls come in their own window, so our window - * loses focus. Our policy is to not draw the focus ring if + * loses focus. Our policy is to not draw the cursor ring if * our window is not focused, but this is an exception since * the user can still navigate the web page with the zoom * controls showing. */ - // If our window has lost focus, stop drawing the focus ring - mDrawFocusRing = false; + // If our window has lost focus, stop drawing the cursor ring + mDrawCursorRing = false; } mGotKeyDown = false; mShiftIsPressed = false; if (mNativeClass != 0) { nativeRecordButtons(false, false, true); } + setFocusControllerInactive(); } invalidate(); super.onWindowFocusChanged(hasWindowFocus); } + /* + * Pass a message to WebCore Thread, telling the WebCore::Page's + * FocusController to be "inactive" so that it will + * not draw the blinking cursor. It gets set to "active" to draw the cursor + * in WebViewCore.cpp, when the WebCore thread receives key events/clicks. + */ + /* package */ void setFocusControllerInactive() { + // Do not need to also check whether mWebViewCore is null, because + // mNativeClass is only set if mWebViewCore is non null + if (mNativeClass == 0) return; + mWebViewCore.sendMessage(EventHub.SET_ACTIVE, 0, 0); + } + @Override protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "MT focusChanged " + focused + ", " + direction); } if (focused) { // When we regain focus, if we have window focus, resume drawing - // the focus ring, and add the TextView if necessary. + // the cursor ring if (hasWindowFocus()) { - mDrawFocusRing = true; - if (mNeedsUpdateTextEntry) { - updateTextEntry(); - mNeedsUpdateTextEntry = false; - } + mDrawCursorRing = true; if (mNativeClass != 0) { nativeRecordButtons(true, false, true); } @@ -3540,12 +3583,13 @@ public class WebView extends AbsoluteLayout } } else { // When we lost focus, unless focus went to the TextView (which is - // true if we are in editing mode), stop drawing the focus ring. + // true if we are in editing mode), stop drawing the cursor ring. if (!inEditingMode()) { - mDrawFocusRing = false; + mDrawCursorRing = false; if (mNativeClass != 0) { nativeRecordButtons(false, false, true); } + setFocusControllerInactive(); } mGotKeyDown = false; } @@ -3557,13 +3601,20 @@ public class WebView extends AbsoluteLayout protected void onSizeChanged(int w, int h, int ow, int oh) { super.onSizeChanged(w, h, ow, oh); // Center zooming to the center of the screen. - mZoomCenterX = getViewWidth() * .5f; - mZoomCenterY = getViewHeight() * .5f; + if (mZoomScale == 0) { // unless we're already zooming + mZoomCenterX = getViewWidth() * .5f; + mZoomCenterY = getViewHeight() * .5f; + } // update mMinZoomScale if the minimum zoom scale is not fixed if (!mMinZoomScaleFixed) { - mMinZoomScale = (float) getViewWidth() - / Math.max(ZOOM_OUT_WIDTH, mContentWidth); + // when change from narrow screen to wide screen, the new viewWidth + // can be wider than the old content width. We limit the minimum + // scale to 1.0f. The proper minimum scale will be calculated when + // the new picture shows up. + mMinZoomScale = Math.min(1.0f, (float) getViewWidth() + / (mDrawHistory ? mHistoryPicture.getWidth() + : mZoomOverviewWidth)); } // we always force, in case our height changed, in which case we still @@ -3574,10 +3625,11 @@ public class WebView extends AbsoluteLayout @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); + sendOurVisibleRect(); } - - + + @Override public boolean dispatchKeyEvent(KeyEvent event) { boolean dispatch = true; @@ -3621,7 +3673,7 @@ public class WebView extends AbsoluteLayout return false; } - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, ev + " at " + ev.getEventTime() + " mTouchMode=" + mTouchMode); } @@ -3638,20 +3690,17 @@ public class WebView extends AbsoluteLayout if (x > getViewWidth() - 1) { x = getViewWidth() - 1; } - if (y > getViewHeight() - 1) { - y = getViewHeight() - 1; + if (y > getViewHeightWithTitle() - 1) { + y = getViewHeightWithTitle() - 1; } // pass the touch events from UI thread to WebCore thread - if (mForwardTouchEvents && mTouchMode != SCROLL_ZOOM_OUT - && mTouchMode != SCROLL_ZOOM_ANIMATION_IN - && mTouchMode != SCROLL_ZOOM_ANIMATION_OUT - && (action != MotionEvent.ACTION_MOVE || - eventTime - mLastSentTouchTime > TOUCH_SENT_INTERVAL)) { + if (mForwardTouchEvents && (action != MotionEvent.ACTION_MOVE + || eventTime - mLastSentTouchTime > TOUCH_SENT_INTERVAL)) { WebViewCore.TouchEventData ted = new WebViewCore.TouchEventData(); ted.mAction = action; - ted.mX = viewToContent((int) x + mScrollX); - ted.mY = viewToContent((int) y + mScrollY); + ted.mX = viewToContentX((int) x + mScrollX); + ted.mY = viewToContentY((int) y + mScrollY); mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); mLastSentTouchTime = eventTime; } @@ -3661,15 +3710,11 @@ public class WebView extends AbsoluteLayout switch (action) { case MotionEvent.ACTION_DOWN: { - if (mTouchMode == SCROLL_ZOOM_ANIMATION_IN - || mTouchMode == SCROLL_ZOOM_ANIMATION_OUT) { - // no interaction while animation is in progress - break; - } else if (mTouchMode == SCROLL_ZOOM_OUT) { - mLastScrollX = mZoomScrollX; - mLastScrollY = mZoomScrollY; - // If two taps are close, ignore the first tap - } else if (!mScroller.isFinished()) { + mPreventDrag = PREVENT_DRAG_NO; + if (!mScroller.isFinished()) { + // stop the current scroll animation, but if this is + // the start of a fling, allow it to add to the current + // fling's velocity mScroller.abortAnimation(); mTouchMode = TOUCH_DRAG_START_MODE; mPrivateHandler.removeMessages(RESUME_WEBCORE_UPDATE); @@ -3677,22 +3722,35 @@ public class WebView extends AbsoluteLayout mSelectX = mScrollX + (int) x; mSelectY = mScrollY + (int) y; mTouchMode = TOUCH_SELECT_MODE; - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "select=" + mSelectX + "," + mSelectY); } - nativeMoveSelection(viewToContent(mSelectX) - , viewToContent(mSelectY), false); + nativeMoveSelection(viewToContentX(mSelectX), + viewToContentY(mSelectY), false); mTouchSelection = mExtendSelection = true; + } else if (mPrivateHandler.hasMessages(RELEASE_SINGLE_TAP)) { + mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP); + if (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare) { + mTouchMode = TOUCH_DOUBLE_TAP_MODE; + } else { + // commit the short press action for the previous tap + doShortPress(); + // continue, mTouchMode should be still TOUCH_INIT_MODE + } } else { mTouchMode = TOUCH_INIT_MODE; - mPreventDrag = mForwardTouchEvents; + mPreventDrag = mForwardTouchEvents ? PREVENT_DRAG_MAYBE_YES + : PREVENT_DRAG_NO; + mWebViewCore.sendMessage( + EventHub.UPDATE_FRAME_CACHE_IF_LOADING); if (mLogEvent && eventTime - mLastTouchUpTime < 1000) { EventLog.writeEvent(EVENT_LOG_DOUBLE_TAP_DURATION, (eventTime - mLastTouchUpTime), eventTime); } } // Trigger the link - if (mTouchMode == TOUCH_INIT_MODE) { + if (mTouchMode == TOUCH_INIT_MODE + || mTouchMode == TOUCH_DOUBLE_TAP_MODE) { mPrivateHandler.sendMessageDelayed(mPrivateHandler .obtainMessage(SWITCH_TO_SHORTPRESS), TAP_TIMEOUT); } @@ -3705,40 +3763,38 @@ public class WebView extends AbsoluteLayout break; } case MotionEvent.ACTION_MOVE: { - if (mTouchMode == TOUCH_DONE_MODE - || mTouchMode == SCROLL_ZOOM_ANIMATION_IN - || mTouchMode == SCROLL_ZOOM_ANIMATION_OUT) { + if (mTouchMode == TOUCH_DONE_MODE) { // no dragging during scroll zoom animation break; } - if (mTouchMode == SCROLL_ZOOM_OUT) { - // while fully zoomed out, move the virtual window - moveZoomScrollWindow(x, y); - break; - } mVelocityTracker.addMovement(ev); if (mTouchMode != TOUCH_DRAG_MODE) { if (mTouchMode == TOUCH_SELECT_MODE) { mSelectX = mScrollX + (int) x; mSelectY = mScrollY + (int) y; - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "xtend=" + mSelectX + "," + mSelectY); } - nativeMoveSelection(viewToContent(mSelectX) - , viewToContent(mSelectY), true); + nativeMoveSelection(viewToContentX(mSelectX), + viewToContentY(mSelectY), true); invalidate(); break; } - if (mPreventDrag || (deltaX * deltaX + deltaY * deltaY) - < mTouchSlopSquare) { + if ((deltaX * deltaX + deltaY * deltaY) < mTouchSlopSquare) { + break; + } + if (mPreventDrag == PREVENT_DRAG_MAYBE_YES) { + // track mLastTouchTime as we may need to do fling at + // ACTION_UP + mLastTouchTime = eventTime; break; } - if (mTouchMode == TOUCH_SHORTPRESS_MODE || mTouchMode == TOUCH_SHORTPRESS_START_MODE) { mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); - } else if (mTouchMode == TOUCH_INIT_MODE) { + } else if (mTouchMode == TOUCH_INIT_MODE + || mTouchMode == TOUCH_DOUBLE_TAP_MODE) { mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); } @@ -3755,24 +3811,22 @@ public class WebView extends AbsoluteLayout mTouchMode = TOUCH_DRAG_MODE; WebViewCore.pauseUpdate(mWebViewCore); - int contentX = viewToContent((int) x + mScrollX); - int contentY = viewToContent((int) y + mScrollY); - if (inEditingMode()) { - mTextEntry.updateCachedTextfield(); - } - nativeClearFocus(contentX, contentY); - // remove the zoom anchor if there is any - if (mZoomScale != 0) { - mWebViewCore - .sendMessage(EventHub.SET_SNAP_ANCHOR, 0, 0); + if (!mDragFromTextInput) { + nativeHideCursor(); } WebSettings settings = getSettings(); if (settings.supportZoom() && settings.getBuiltInZoomControls() && !mZoomButtonsController.isVisible() - && (canZoomScrollOut() || - mMinZoomScale < mMaxZoomScale)) { + && mMinZoomScale < mMaxZoomScale) { mZoomButtonsController.setVisible(true); + int count = settings.getDoubleTapToastCount(); + if (mInZoomOverview && count > 0) { + settings.setDoubleTapToastCount(--count); + Toast.makeText(mContext, + com.android.internal.R.string.double_tap_toast, + Toast.LENGTH_LONG).show(); + } } } @@ -3796,7 +3850,7 @@ public class WebView extends AbsoluteLayout } // reverse direction means lock in the snap mode if ((ax > MAX_SLOPE_FOR_DIAG * ay) && - ((mSnapPositive && + ((mSnapPositive && deltaX < -mMinLockSnapReverseDistance) || (!mSnapPositive && deltaX > mMinLockSnapReverseDistance))) { @@ -3810,9 +3864,9 @@ public class WebView extends AbsoluteLayout } // reverse direction means lock in the snap mode if ((ay > MAX_SLOPE_FOR_DIAG * ax) && - ((mSnapPositive && + ((mSnapPositive && deltaY < -mMinLockSnapReverseDistance) - || (!mSnapPositive && + || (!mSnapPositive && deltaY > mMinLockSnapReverseDistance))) { mSnapScrollMode = SNAP_Y_LOCK; } @@ -3821,11 +3875,25 @@ public class WebView extends AbsoluteLayout if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_X_LOCK) { - scrollBy(deltaX, 0); + if (deltaX == 0) { + // keep the scrollbar on the screen even there is no + // scroll + awakenScrollBars(ViewConfiguration + .getScrollDefaultDelay(), false); + } else { + scrollBy(deltaX, 0); + } mLastTouchX = x; } else if (mSnapScrollMode == SNAP_Y || mSnapScrollMode == SNAP_Y_LOCK) { - scrollBy(0, deltaY); + if (deltaY == 0) { + // keep the scrollbar on the screen even there is no + // scroll + awakenScrollBars(ViewConfiguration + .getScrollDefaultDelay(), false); + } else { + scrollBy(0, deltaY); + } mLastTouchY = y; } else { scrollBy(deltaX, deltaY); @@ -3838,12 +3906,11 @@ public class WebView extends AbsoluteLayout if (!getSettings().getBuiltInZoomControls()) { boolean showPlusMinus = mMinZoomScale < mMaxZoomScale; - boolean showMagnify = canZoomScrollOut(); - if (mZoomControls != null && (showPlusMinus || showMagnify)) { + if (mZoomControls != null && showPlusMinus) { if (mZoomControls.getVisibility() == View.VISIBLE) { mPrivateHandler.removeCallbacks(mZoomControlRunnable); } else { - mZoomControls.show(showPlusMinus, showMagnify); + mZoomControls.show(showPlusMinus, false); } mPrivateHandler.postDelayed(mZoomControlRunnable, ZOOM_CONTROLS_TIMEOUT); @@ -3851,6 +3918,9 @@ public class WebView extends AbsoluteLayout } if (done) { + // keep the scrollbar on the screen even there is no scroll + awakenScrollBars(ViewConfiguration.getScrollDefaultDelay(), + false); // return false to indicate that we can't pan out of the // view space return false; @@ -3860,41 +3930,54 @@ public class WebView extends AbsoluteLayout case MotionEvent.ACTION_UP: { mLastTouchUpTime = eventTime; switch (mTouchMode) { - case TOUCH_INIT_MODE: // tap - case TOUCH_SHORTPRESS_START_MODE: - case TOUCH_SHORTPRESS_MODE: + case TOUCH_DOUBLE_TAP_MODE: // double tap mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); - mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); mTouchMode = TOUCH_DONE_MODE; - doShortPress(); + doDoubleTap(); break; case TOUCH_SELECT_MODE: commitCopy(); mTouchSelection = false; break; - case SCROLL_ZOOM_ANIMATION_IN: - case SCROLL_ZOOM_ANIMATION_OUT: - // no action during scroll animation - break; - case SCROLL_ZOOM_OUT: - if (LOGV_ENABLED) { - Log.v(LOGTAG, "ACTION_UP SCROLL_ZOOM_OUT" - + " eventTime - mLastTouchTime=" - + (eventTime - mLastTouchTime)); - } - // for now, always zoom back when the drag completes - if (true || eventTime - mLastTouchTime < TAP_TIMEOUT) { - // but if we tap, zoom in where we tap - if (eventTime - mLastTouchTime < TAP_TIMEOUT) { - zoomScrollTap(x, y); + case TOUCH_INIT_MODE: // tap + case TOUCH_SHORTPRESS_START_MODE: + case TOUCH_SHORTPRESS_MODE: + mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); + mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); + if ((deltaX * deltaX + deltaY * deltaY) > mTouchSlopSquare) { + Log.w(LOGTAG, "Miss a drag as we are waiting for" + + " WebCore's response for touch down."); + if (computeHorizontalScrollExtent() < computeHorizontalScrollRange() + || computeVerticalScrollExtent() < computeVerticalScrollRange()) { + // we will not rewrite drag code here, but we + // will try fling if it applies. + WebViewCore.pauseUpdate(mWebViewCore); + // fall through to TOUCH_DRAG_MODE + } else { + break; + } + } else { + if (mPreventDrag == PREVENT_DRAG_MAYBE_YES) { + // if mPreventDrag is not confirmed, treat it as + // no so that it won't block tap or double tap. + mPreventDrag = PREVENT_DRAG_NO; } - // start zooming in back to the original view - setZoomScrollIn(); - mTouchMode = SCROLL_ZOOM_ANIMATION_IN; - invalidate(); + if (mPreventDrag == PREVENT_DRAG_NO) { + if (mTouchMode == TOUCH_INIT_MODE) { + mPrivateHandler.sendMessageDelayed( + mPrivateHandler.obtainMessage( + RELEASE_SINGLE_TAP), + ViewConfiguration.getDoubleTapTimeout()); + } else { + mTouchMode = TOUCH_DONE_MODE; + doShortPress(); + } + } + break; } - break; case TOUCH_DRAG_MODE: + // redraw in high-quality, as we're done dragging + invalidate(); // if the user waits a while w/o moving before the // up, we don't want to do a fling if (eventTime - mLastTouchTime <= MIN_FLING_TIME) { @@ -3902,6 +3985,7 @@ public class WebView extends AbsoluteLayout doFling(); break; } + mLastVelocity = 0; WebViewCore.resumeUpdate(mWebViewCore); break; case TOUCH_DRAG_START_MODE: @@ -3926,27 +4010,19 @@ public class WebView extends AbsoluteLayout mVelocityTracker.recycle(); mVelocityTracker = null; } - if (mTouchMode == SCROLL_ZOOM_OUT || - mTouchMode == SCROLL_ZOOM_ANIMATION_IN) { - scrollTo(mZoomScrollX, mZoomScrollY); - } else if (mTouchMode == TOUCH_DRAG_MODE) { + if (mTouchMode == TOUCH_DRAG_MODE) { WebViewCore.resumeUpdate(mWebViewCore); } mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); mTouchMode = TOUCH_DONE_MODE; - int contentX = viewToContent((int) mLastTouchX + mScrollX); - int contentY = viewToContent((int) mLastTouchY + mScrollY); - if (inEditingMode()) { - mTextEntry.updateCachedTextfield(); - } - nativeClearFocus(contentX, contentY); + nativeHideCursor(); break; } } return true; } - + private long mTrackballFirstTime = 0; private long mTrackballLastTime = 0; private float mTrackballRemainsX = 0.0f; @@ -3968,14 +4044,14 @@ public class WebView extends AbsoluteLayout private boolean mShiftIsPressed = false; private boolean mTrackballDown = false; private long mTrackballUpTime = 0; - private long mLastFocusTime = 0; - private Rect mLastFocusBounds; + private long mLastCursorTime = 0; + private Rect mLastCursorBounds; // Set by default; BrowserActivity clears to interpret trackball data - // directly for movement. Currently, the framework only passes + // directly for movement. Currently, the framework only passes // arrow key events, not trackball events, from one child to the next private boolean mMapTrackballToArrowKeys = true; - + public void setMapTrackballToArrowKeys(boolean setMap) { mMapTrackballToArrowKeys = setMap; } @@ -3993,26 +4069,29 @@ public class WebView extends AbsoluteLayout return true; } if (ev.getAction() == MotionEvent.ACTION_DOWN) { - mPrivateHandler.removeMessages(SWITCH_TO_ENTER); + if (mShiftIsPressed) { + return true; // discard press if copy in progress + } mTrackballDown = true; - if (mNativeClass != 0) { - nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true); + if (mNativeClass == 0) { + return false; } - if (time - mLastFocusTime <= TRACKBALL_TIMEOUT - && !mLastFocusBounds.equals(nativeGetFocusRingBounds())) { - nativeSelectBestAt(mLastFocusBounds); + nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true); + if (time - mLastCursorTime <= TRACKBALL_TIMEOUT + && !mLastCursorBounds.equals(nativeGetCursorRingBounds())) { + nativeSelectBestAt(mLastCursorBounds); } - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "onTrackballEvent down ev=" + ev - + " time=" + time - + " mLastFocusTime=" + mLastFocusTime); + + " time=" + time + + " mLastCursorTime=" + mLastCursorTime); } if (isInTouchMode()) requestFocusFromTouch(); return false; // let common code in onKeyDown at it - } + } if (ev.getAction() == MotionEvent.ACTION_UP) { - // LONG_PRESS_ENTER is set in common onKeyDown - mPrivateHandler.removeMessages(LONG_PRESS_ENTER); + // LONG_PRESS_CENTER is set in common onKeyDown + mPrivateHandler.removeMessages(LONG_PRESS_CENTER); mTrackballDown = false; mTrackballUpTime = time; if (mShiftIsPressed) { @@ -4021,43 +4100,39 @@ public class WebView extends AbsoluteLayout } else { mExtendSelection = true; } + return true; // discard press if copy in progress } - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "onTrackballEvent up ev=" + ev - + " time=" + time + + " time=" + time ); } return false; // let common code in onKeyUp at it } if (mMapTrackballToArrowKeys && mShiftIsPressed == false) { - if (LOGV_ENABLED) Log.v(LOGTAG, "onTrackballEvent gmail quit"); + if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent gmail quit"); return false; } - // no move if we're still waiting on SWITCH_TO_ENTER timeout - if (mTouchMode == TOUCH_DOUBLECLICK_MODE) { - if (LOGV_ENABLED) Log.v(LOGTAG, "onTrackballEvent 2 click quit"); - return true; - } if (mTrackballDown) { - if (LOGV_ENABLED) Log.v(LOGTAG, "onTrackballEvent down quit"); + if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent down quit"); return true; // discard move if trackball is down } if (time - mTrackballUpTime < TRACKBALL_TIMEOUT) { - if (LOGV_ENABLED) Log.v(LOGTAG, "onTrackballEvent up timeout quit"); + if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent up timeout quit"); return true; } // TODO: alternatively we can do panning as touch does switchOutDrawHistory(); if (time - mTrackballLastTime > TRACKBALL_TIMEOUT) { - if (LOGV_ENABLED) { - Log.v(LOGTAG, "onTrackballEvent time=" + if (DebugFlags.WEB_VIEW) { + Log.v(LOGTAG, "onTrackballEvent time=" + time + " last=" + mTrackballLastTime); } mTrackballFirstTime = time; mTrackballXMove = mTrackballYMove = 0; } mTrackballLastTime = time; - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "onTrackballEvent ev=" + ev + " time=" + time); } mTrackballRemainsX += ev.getX(); @@ -4065,7 +4140,7 @@ public class WebView extends AbsoluteLayout doTrackball(time); return true; } - + void moveSelection(float xRate, float yRate) { if (mNativeClass == 0) return; @@ -4079,8 +4154,8 @@ public class WebView extends AbsoluteLayout , mSelectX)); mSelectY = Math.min(maxY, Math.max(mScrollY - SELECT_CURSOR_OFFSET , mSelectY)); - if (LOGV_ENABLED) { - Log.v(LOGTAG, "moveSelection" + if (DebugFlags.WEB_VIEW) { + Log.v(LOGTAG, "moveSelection" + " mSelectX=" + mSelectX + " mSelectY=" + mSelectY + " mScrollX=" + mScrollX @@ -4089,13 +4164,13 @@ public class WebView extends AbsoluteLayout + " yRate=" + yRate ); } - nativeMoveSelection(viewToContent(mSelectX) - , viewToContent(mSelectY), mExtendSelection); + nativeMoveSelection(viewToContentX(mSelectX), + viewToContentY(mSelectY), mExtendSelection); int scrollX = mSelectX < mScrollX ? -SELECT_CURSOR_OFFSET - : mSelectX > maxX - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET + : mSelectX > maxX - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET : 0; int scrollY = mSelectY < mScrollY ? -SELECT_CURSOR_OFFSET - : mSelectY > maxY - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET + : mSelectY > maxY - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET : 0; pinScrollBy(scrollX, scrollY, true, 0); Rect select = new Rect(mSelectX, mSelectY, mSelectX + 1, mSelectY + 1); @@ -4152,7 +4227,7 @@ public class WebView extends AbsoluteLayout if (elapsed == 0) { elapsed = TRACKBALL_TIMEOUT; } - float xRate = mTrackballRemainsX * 1000 / elapsed; + float xRate = mTrackballRemainsX * 1000 / elapsed; float yRate = mTrackballRemainsY * 1000 / elapsed; if (mShiftIsPressed) { moveSelection(xRate, yRate); @@ -4162,7 +4237,7 @@ public class WebView extends AbsoluteLayout float ax = Math.abs(xRate); float ay = Math.abs(yRate); float maxA = Math.max(ax, ay); - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "doTrackball elapsed=" + elapsed + " xRate=" + xRate + " yRate=" + yRate @@ -4173,25 +4248,6 @@ public class WebView extends AbsoluteLayout int height = mContentHeight - getViewHeight(); if (width < 0) width = 0; if (height < 0) height = 0; - if (mTouchMode == SCROLL_ZOOM_OUT) { - int oldX = mZoomScrollX; - int oldY = mZoomScrollY; - int maxWH = Math.max(width, height); - mZoomScrollX += scaleTrackballX(xRate, maxWH); - mZoomScrollY += scaleTrackballY(yRate, maxWH); - if (LOGV_ENABLED) { - Log.v(LOGTAG, "doTrackball SCROLL_ZOOM_OUT" - + " mZoomScrollX=" + mZoomScrollX - + " mZoomScrollY=" + mZoomScrollY); - } - mZoomScrollX = Math.min(width, Math.max(0, mZoomScrollX)); - mZoomScrollY = Math.min(height, Math.max(0, mZoomScrollY)); - if (oldX != mZoomScrollX || oldY != mZoomScrollY) { - invalidate(); - } - mTrackballRemainsX = mTrackballRemainsY = 0; - return; - } ax = Math.abs(mTrackballRemainsX * TRACKBALL_MULTIPLIER); ay = Math.abs(mTrackballRemainsY * TRACKBALL_MULTIPLIER); maxA = Math.max(ax, ay); @@ -4199,18 +4255,18 @@ public class WebView extends AbsoluteLayout int oldScrollX = mScrollX; int oldScrollY = mScrollY; if (count > 0) { - int selectKeyCode = ax < ay ? mTrackballRemainsY < 0 ? - KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_DPAD_DOWN : + int selectKeyCode = ax < ay ? mTrackballRemainsY < 0 ? + KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_DPAD_DOWN : mTrackballRemainsX < 0 ? KeyEvent.KEYCODE_DPAD_LEFT : KeyEvent.KEYCODE_DPAD_RIGHT; count = Math.min(count, TRACKBALL_MOVE_COUNT); - if (LOGV_ENABLED) { - Log.v(LOGTAG, "doTrackball keyCode=" + selectKeyCode + if (DebugFlags.WEB_VIEW) { + Log.v(LOGTAG, "doTrackball keyCode=" + selectKeyCode + " count=" + count + " mTrackballRemainsX=" + mTrackballRemainsX + " mTrackballRemainsY=" + mTrackballRemainsY); } - if (navHandledKey(selectKeyCode, count, false, time)) { + if (navHandledKey(selectKeyCode, count, false, time, false)) { playSoundEffect(keyCodeToSoundsEffect(selectKeyCode)); } mTrackballRemainsX = mTrackballRemainsY = 0; @@ -4218,12 +4274,12 @@ public class WebView extends AbsoluteLayout if (count >= TRACKBALL_SCROLL_COUNT) { int xMove = scaleTrackballX(xRate, width); int yMove = scaleTrackballY(yRate, height); - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "doTrackball pinScrollBy" + " count=" + count + " xMove=" + xMove + " yMove=" + yMove - + " mScrollX-oldScrollX=" + (mScrollX-oldScrollX) - + " mScrollY-oldScrollY=" + (mScrollY-oldScrollY) + + " mScrollX-oldScrollX=" + (mScrollX-oldScrollX) + + " mScrollY-oldScrollY=" + (mScrollY-oldScrollY) ); } if (Math.abs(mScrollX - oldScrollX) > Math.abs(xMove)) { @@ -4236,24 +4292,28 @@ public class WebView extends AbsoluteLayout pinScrollBy(xMove, yMove, true, 0); } mUserScroll = true; - } - mWebViewCore.sendMessage(EventHub.UNBLOCK_FOCUS); + } + } + + private int computeMaxScrollY() { + int maxContentH = computeVerticalScrollRange() + getTitleHeight(); + return Math.max(maxContentH - getHeight(), getTitleHeight()); } public void flingScroll(int vx, int vy) { int maxX = Math.max(computeHorizontalScrollRange() - getViewWidth(), 0); - int maxY = Math.max(computeVerticalScrollRange() - getViewHeight(), 0); - + int maxY = computeMaxScrollY(); + mScroller.fling(mScrollX, mScrollY, vx, vy, 0, maxX, 0, maxY); invalidate(); } - + private void doFling() { if (mVelocityTracker == null) { return; } int maxX = Math.max(computeHorizontalScrollRange() - getViewWidth(), 0); - int maxY = Math.max(computeVerticalScrollRange() - getViewHeight(), 0); + int maxY = computeMaxScrollY(); mVelocityTracker.computeCurrentVelocity(1000, mMaximumFling); int vx = (int) mVelocityTracker.getXVelocity(); @@ -4266,12 +4326,40 @@ public class WebView extends AbsoluteLayout vx = 0; } } - + if (true /* EMG release: make our fling more like Maps' */) { // maps cuts their velocity in half vx = vx * 3 / 4; vy = vy * 3 / 4; } + if ((maxX == 0 && vy == 0) || (maxY == 0 && vx == 0)) { + WebViewCore.resumeUpdate(mWebViewCore); + return; + } + float currentVelocity = mScroller.getCurrVelocity(); + if (mLastVelocity > 0 && currentVelocity > 0) { + float deltaR = (float) (Math.abs(Math.atan2(mLastVelY, mLastVelX) + - Math.atan2(vy, vx))); + final float circle = (float) (Math.PI) * 2.0f; + if (deltaR > circle * 0.9f || deltaR < circle * 0.1f) { + vx += currentVelocity * mLastVelX / mLastVelocity; + vy += currentVelocity * mLastVelY / mLastVelocity; + if (DebugFlags.WEB_VIEW) { + Log.v(LOGTAG, "doFling vx= " + vx + " vy=" + vy); + } + } else if (DebugFlags.WEB_VIEW) { + Log.v(LOGTAG, "doFling missed " + deltaR / circle); + } + } else if (DebugFlags.WEB_VIEW) { + Log.v(LOGTAG, "doFling start last=" + mLastVelocity + + " current=" + currentVelocity + + " vx=" + vx + " vy=" + vy + + " maxX=" + maxX + " maxY=" + maxY + + " mScrollX=" + mScrollX + " mScrollY=" + mScrollY); + } + mLastVelX = vx; + mLastVelY = vy; + mLastVelocity = (float) Math.hypot(vx, vy); mScroller.fling(mScrollX, mScrollY, -vx, -vy, 0, maxX, 0, maxY); // TODO: duration is calculated based on velocity, if the range is @@ -4280,11 +4368,14 @@ public class WebView extends AbsoluteLayout // resume the webcore update. final int time = mScroller.getDuration(); mPrivateHandler.sendEmptyMessageDelayed(RESUME_WEBCORE_UPDATE, time); + awakenScrollBars(time); invalidate(); } private boolean zoomWithPreview(float scale) { float oldScale = mActualScale; + mInitialScrollX = mScrollX; + mInitialScrollY = mScrollY; // snap to DEFAULT_SCALE if it is close if (scale > (mDefaultScale - 0.05) && scale < (mDefaultScale + 0.05)) { @@ -4299,6 +4390,9 @@ public class WebView extends AbsoluteLayout mInvInitialZoomScale = 1.0f / oldScale; mInvFinalZoomScale = 1.0f / mActualScale; mZoomScale = mActualScale; + if (!mInZoomOverview) { + mLastScale = scale; + } invalidate(); return true; } else { @@ -4327,7 +4421,7 @@ public class WebView extends AbsoluteLayout } if (mZoomControls == null) { mZoomControls = createZoomControls(); - + /* * need to be set to VISIBLE first so that getMeasuredHeight() in * {@link #onSizeChanged()} can return the measured value for proper @@ -4336,7 +4430,7 @@ public class WebView extends AbsoluteLayout mZoomControls.setVisibility(View.VISIBLE); mZoomControlRunnable = new Runnable() { public void run() { - + /* Don't dismiss the controls if the user has * focus on them. Wait and check again later. */ @@ -4374,21 +4468,13 @@ public class WebView extends AbsoluteLayout zoomOut(); } }); - zoomControls.setOnZoomMagnifyClickListener(new OnClickListener() { - public void onClick(View v) { - mPrivateHandler.removeCallbacks(mZoomControlRunnable); - mPrivateHandler.postDelayed(mZoomControlRunnable, - ZOOM_CONTROLS_TIMEOUT); - zoomScrollOut(); - } - }); return zoomControls; } /** * Gets the {@link ZoomButtonsController} which can be used to add * additional buttons to the zoom controls window. - * + * * @return The instance of {@link ZoomButtonsController} used by this class, * or null if it is unavailable. * @hide @@ -4404,7 +4490,18 @@ public class WebView extends AbsoluteLayout public boolean zoomIn() { // TODO: alternatively we can disallow this during draw history mode switchOutDrawHistory(); - return zoomWithPreview(mActualScale * 1.25f); + // Center zooming to the center of the screen. + if (mInZoomOverview) { + // if in overview mode, bring it back to normal mode + mLastTouchX = getViewWidth() * .5f; + mLastTouchY = getViewHeight() * .5f; + doDoubleTap(); + return true; + } else { + mZoomCenterX = getViewWidth() * .5f; + mZoomCenterY = getViewHeight() * .5f; + return zoomWithPreview(mActualScale * 1.25f); + } } /** @@ -4414,7 +4511,18 @@ public class WebView extends AbsoluteLayout public boolean zoomOut() { // TODO: alternatively we can disallow this during draw history mode switchOutDrawHistory(); - return zoomWithPreview(mActualScale * 0.8f); + float scale = mActualScale * 0.8f; + if (scale < (mMinZoomScale + 0.1f) + && mWebViewCore.getSettings().getUseWideViewPort()) { + // when zoom out to min scale, switch to overview mode + doDoubleTap(); + return true; + } else { + // Center zooming to the center of the screen. + mZoomCenterX = getViewWidth() * .5f; + mZoomCenterY = getViewHeight() * .5f; + return zoomWithPreview(scale); + } } private void updateSelection() { @@ -4422,23 +4530,91 @@ public class WebView extends AbsoluteLayout return; } // mLastTouchX and mLastTouchY are the point in the current viewport - int contentX = viewToContent((int) mLastTouchX + mScrollX); - int contentY = viewToContent((int) mLastTouchY + mScrollY); + int contentX = viewToContentX((int) mLastTouchX + mScrollX); + int contentY = viewToContentY((int) mLastTouchY + mScrollY); Rect rect = new Rect(contentX - mNavSlop, contentY - mNavSlop, contentX + mNavSlop, contentY + mNavSlop); - // If we were already focused on a textfield, update its cache. - if (inEditingMode()) { - mTextEntry.updateCachedTextfield(); - } nativeSelectBestAt(rect); } + /** + * Scroll the focused text field/area to match the WebTextView + * @param xPercent New x position of the WebTextView from 0 to 1. + * @param y New y position of the WebTextView in view coordinates + */ + /*package*/ void scrollFocusedTextInput(float xPercent, int y) { + if (!inEditingMode() || mWebViewCore == null) { + return; + } + mWebViewCore.sendMessage(EventHub.SCROLL_TEXT_INPUT, + // Since this position is relative to the top of the text input + // field, we do not need to take the title bar's height into + // consideration. + viewToContentDimension(y), + new Float(xPercent)); + } + + /** + * Set our starting point and time for a drag from the WebTextView. + */ + /*package*/ void initiateTextFieldDrag(float x, float y, long eventTime) { + if (!inEditingMode()) { + return; + } + mLastTouchX = x + (float) (mWebTextView.getLeft() - mScrollX); + mLastTouchY = y + (float) (mWebTextView.getTop() - mScrollY); + mLastTouchTime = eventTime; + if (!mScroller.isFinished()) { + abortAnimation(); + mPrivateHandler.removeMessages(RESUME_WEBCORE_UPDATE); + } + mSnapScrollMode = SNAP_NONE; + mVelocityTracker = VelocityTracker.obtain(); + mTouchMode = TOUCH_DRAG_START_MODE; + } + + /** + * Given a motion event from the WebTextView, set its location to our + * coordinates, and handle the event. + */ + /*package*/ boolean textFieldDrag(MotionEvent event) { + if (!inEditingMode()) { + return false; + } + mDragFromTextInput = true; + event.offsetLocation((float) (mWebTextView.getLeft() - mScrollX), + (float) (mWebTextView.getTop() - mScrollY)); + boolean result = onTouchEvent(event); + mDragFromTextInput = false; + return result; + } + + /** + * Do a touch up from a WebTextView. This will be handled by webkit to + * change the selection. + * @param event MotionEvent in the WebTextView's coordinates. + */ + /*package*/ void touchUpOnTextField(MotionEvent event) { + if (!inEditingMode()) { + return; + } + int x = viewToContentX((int) event.getX() + mWebTextView.getLeft()); + int y = viewToContentY((int) event.getY() + mWebTextView.getTop()); + // In case the soft keyboard has been dismissed, bring it back up. + InputMethodManager.getInstance(getContext()).showSoftInput(mWebTextView, + 0); + if (nativeFocusNodePointer() != nativeCursorNodePointer()) { + nativeMotionUp(x, y, mNavSlop); + } + nativeTextInputMotionUp(x, y); + } + /*package*/ void shortPressOnTextField() { if (inEditingMode()) { - View v = mTextEntry; - int x = viewToContent((v.getLeft() + v.getRight()) >> 1); - int y = viewToContent((v.getTop() + v.getBottom()) >> 1); - nativeMotionUp(x, y, mNavSlop, true); + View v = mWebTextView; + int x = viewToContentX((v.getLeft() + v.getRight()) >> 1); + int y = viewToContentY((v.getTop() + v.getBottom()) >> 1); + nativeTextInputMotionUp(x, y); } } @@ -4448,31 +4624,81 @@ public class WebView extends AbsoluteLayout } switchOutDrawHistory(); // mLastTouchX and mLastTouchY are the point in the current viewport - int contentX = viewToContent((int) mLastTouchX + mScrollX); - int contentY = viewToContent((int) mLastTouchY + mScrollY); - if (nativeMotionUp(contentX, contentY, mNavSlop, true)) { + int contentX = viewToContentX((int) mLastTouchX + mScrollX); + int contentY = viewToContentY((int) mLastTouchY + mScrollY); + if (nativeMotionUp(contentX, contentY, mNavSlop)) { if (mLogEvent) { Checkin.updateStats(mContext.getContentResolver(), Checkin.Stats.Tag.BROWSER_SNAP_CENTER, 1, 0.0); } } - if (nativeUpdateFocusNode() && !mFocusNode.mIsTextField - && !mFocusNode.mIsTextArea) { + if (nativeHasCursorNode() && !nativeCursorIsTextInput()) { playSoundEffect(SoundEffectConstants.CLICK); } } + private void doDoubleTap() { + if (mWebViewCore.getSettings().getUseWideViewPort() == false) { + return; + } + mZoomCenterX = mLastTouchX; + mZoomCenterY = mLastTouchY; + mInZoomOverview = !mInZoomOverview; + // remove the zoom control after double tap + WebSettings settings = getSettings(); + if (settings.getBuiltInZoomControls()) { + if (mZoomButtonsController.isVisible()) { + mZoomButtonsController.setVisible(false); + } + } else { + if (mZoomControlRunnable != null) { + mPrivateHandler.removeCallbacks(mZoomControlRunnable); + } + if (mZoomControls != null) { + mZoomControls.hide(); + } + } + settings.setDoubleTapToastCount(0); + if (mInZoomOverview) { + // Force the titlebar fully reveal in overview mode + if (mScrollY < getTitleHeight()) mScrollY = 0; + zoomWithPreview((float) getViewWidth() / mZoomOverviewWidth); + } else { + // mLastTouchX and mLastTouchY are the point in the current viewport + int contentX = viewToContentX((int) mLastTouchX + mScrollX); + int contentY = viewToContentY((int) mLastTouchY + mScrollY); + int left = nativeGetBlockLeftEdge(contentX, contentY, mActualScale); + if (left != NO_LEFTEDGE) { + // add a 5pt padding to the left edge. Re-calculate the zoom + // center so that the new scroll x will be on the left edge. + mZoomCenterX = left < 5 ? 0 : (left - 5) * mLastScale + * mActualScale / (mLastScale - mActualScale); + } + zoomWithPreview(mLastScale); + } + } + // Called by JNI to handle a touch on a node representing an email address, // address, or phone number private void overrideLoading(String url) { mCallbackProxy.uiOverrideUrlLoading(url); } + // called by JNI + private void sendPluginState(int state) { + WebViewCore.PluginStateData psd = new WebViewCore.PluginStateData(); + psd.mFrame = nativeCursorFramePointer(); + psd.mNode = nativeCursorNodePointer(); + psd.mState = state; + mWebViewCore.sendMessage(EventHub.PLUGIN_STATE, psd); + } + @Override public boolean requestFocus(int direction, Rect previouslyFocusedRect) { boolean result = false; if (inEditingMode()) { - result = mTextEntry.requestFocus(direction, previouslyFocusedRect); + result = mWebTextView.requestFocus(direction, + previouslyFocusedRect); } else { result = super.requestFocus(direction, previouslyFocusedRect); if (mWebViewCore.getSettings().getNeedInitialFocus()) { @@ -4496,8 +4722,8 @@ public class WebView extends AbsoluteLayout default: return result; } - if (mNativeClass != 0 && !nativeUpdateFocusNode()) { - navHandledKey(fakeKeyDirection, 1, true, 0); + if (mNativeClass != 0 && !nativeHasCursorNode()) { + navHandledKey(fakeKeyDirection, 1, true, 0, true); } } } @@ -4517,8 +4743,8 @@ public class WebView extends AbsoluteLayout int measuredWidth = widthSize; // Grab the content size from WebViewCore. - int contentHeight = mContentHeight; - int contentWidth = mContentWidth; + int contentHeight = contentToViewDimension(mContentHeight); + int contentWidth = contentToViewDimension(mContentWidth); // Log.d(LOGTAG, "------- measure " + heightMode); @@ -4559,20 +4785,25 @@ public class WebView extends AbsoluteLayout rect.offset(child.getLeft() - child.getScrollX(), child.getTop() - child.getScrollY()); - int height = getHeight() - getHorizontalScrollbarHeight(); + int height = getViewHeightWithTitle(); int screenTop = mScrollY; int screenBottom = screenTop + height; int scrollYDelta = 0; - if (rect.bottom > screenBottom && rect.top > screenTop) { - if (rect.height() > height) { - scrollYDelta += (rect.top - screenTop); + if (rect.bottom > screenBottom) { + int oneThirdOfScreenHeight = height / 3; + if (rect.height() > 2 * oneThirdOfScreenHeight) { + // If the rectangle is too tall to fit in the bottom two thirds + // of the screen, place it at the top. + scrollYDelta = rect.top - screenTop; } else { - scrollYDelta += (rect.bottom - screenBottom); + // If the rectangle will still fit on screen, we want its + // top to be in the top third of the screen. + scrollYDelta = rect.top - (screenTop + oneThirdOfScreenHeight); } } else if (rect.top < screenTop) { - scrollYDelta -= (screenTop - rect.top); + scrollYDelta = rect.top - screenTop; } int width = getWidth() - getVerticalScrollbarWidth(); @@ -4597,33 +4828,40 @@ public class WebView extends AbsoluteLayout return false; } - + /* package */ void replaceTextfieldText(int oldStart, int oldEnd, String replace, int newStart, int newEnd) { - HashMap arg = new HashMap(); - arg.put("focusData", new WebViewCore.FocusData(mFocusData)); - arg.put("replace", replace); - arg.put("start", new Integer(newStart)); - arg.put("end", new Integer(newEnd)); + WebViewCore.ReplaceTextData arg = new WebViewCore.ReplaceTextData(); + arg.mReplace = replace; + arg.mNewStart = newStart; + arg.mNewEnd = newEnd; mTextGeneration++; + arg.mTextGeneration = mTextGeneration; mWebViewCore.sendMessage(EventHub.REPLACE_TEXT, oldStart, oldEnd, arg); } /* package */ void passToJavaScript(String currentText, KeyEvent event) { - HashMap arg = new HashMap(); - arg.put("focusData", new WebViewCore.FocusData(mFocusData)); - arg.put("event", event); - arg.put("currentText", currentText); + if (nativeCursorWantsKeyEvents() && !nativeCursorMatchesFocus()) { + mWebViewCore.sendMessage(EventHub.CLICK); + if (mWebTextView.mOkayForFocusNotToMatch) { + int select = nativeFocusCandidateIsTextField() ? + nativeFocusCandidateMaxLength() : 0; + setSelection(select, select); + } + } + WebViewCore.JSKeyData arg = new WebViewCore.JSKeyData(); + arg.mEvent = event; + arg.mCurrentText = currentText; // Increase our text generation number, and pass it to webcore thread mTextGeneration++; mWebViewCore.sendMessage(EventHub.PASS_TO_JS, mTextGeneration, 0, arg); // WebKit's document state is not saved until about to leave the page. - // To make sure the host application, like Browser, has the up to date - // document state when it goes to background, we force to save the + // To make sure the host application, like Browser, has the up to date + // document state when it goes to background, we force to save the // document state. mWebViewCore.removeMessages(EventHub.SAVE_DOCUMENT_STATE); mWebViewCore.sendMessageDelayed(EventHub.SAVE_DOCUMENT_STATE, - new WebViewCore.FocusData(mFocusData), 1000); + cursorData(), 1000); } /* package */ WebViewCore getWebViewCore() { @@ -4641,11 +4879,15 @@ public class WebView extends AbsoluteLayout class PrivateHandler extends Handler { @Override public void handleMessage(Message msg) { - if (LOGV_ENABLED) { - Log.v(LOGTAG, msg.what < REMEMBER_PASSWORD || msg.what - > INVAL_RECT_MSG_ID ? Integer.toString(msg.what) + if (DebugFlags.WEB_VIEW) { + Log.v(LOGTAG, msg.what < REMEMBER_PASSWORD || msg.what + > INVAL_RECT_MSG_ID ? Integer.toString(msg.what) : HandlerDebugString[msg.what - REMEMBER_PASSWORD]); } + if (mWebViewCore == null) { + // after WebView's destroy() is called, skip handling messages. + return; + } switch (msg.what) { case REMEMBER_PASSWORD: { mDatabase.setUsernamePassword( @@ -4662,33 +4904,40 @@ public class WebView extends AbsoluteLayout break; } case SWITCH_TO_SHORTPRESS: { + // if mPreventDrag is not confirmed, treat it as no so that + // it won't block panning the page. + if (mPreventDrag == PREVENT_DRAG_MAYBE_YES) { + mPreventDrag = PREVENT_DRAG_NO; + } if (mTouchMode == TOUCH_INIT_MODE) { mTouchMode = TOUCH_SHORTPRESS_START_MODE; updateSelection(); + } else if (mTouchMode == TOUCH_DOUBLE_TAP_MODE) { + mTouchMode = TOUCH_DONE_MODE; } break; } case SWITCH_TO_LONGPRESS: { - if (!mPreventDrag) { + if (mPreventDrag == PREVENT_DRAG_NO) { mTouchMode = TOUCH_DONE_MODE; performLongClick(); - updateTextEntry(); + rebuildWebTextView(); } break; } - case SWITCH_TO_ENTER: - if (LOGV_ENABLED) Log.v(LOGTAG, "SWITCH_TO_ENTER"); - mTouchMode = TOUCH_DONE_MODE; - onKeyUp(KeyEvent.KEYCODE_ENTER - , new KeyEvent(KeyEvent.ACTION_UP - , KeyEvent.KEYCODE_ENTER)); + case RELEASE_SINGLE_TAP: { + if (mPreventDrag == PREVENT_DRAG_NO) { + mTouchMode = TOUCH_DONE_MODE; + doShortPress(); + } break; + } case SCROLL_BY_MSG_ID: setContentScrollBy(msg.arg1, msg.arg2, (Boolean) msg.obj); break; case SYNC_SCROLL_TO_MSG_ID: if (mUserScroll) { - // if user has scrolled explicitly, don't sync the + // if user has scrolled explicitly, don't sync the // scroll position any more mUserScroll = false; break; @@ -4697,7 +4946,7 @@ public class WebView extends AbsoluteLayout case SCROLL_TO_MSG_ID: if (setContentScrollTo(msg.arg1, msg.arg2)) { // if we can't scroll to the exact position due to pin, - // send a message to WebCore to re-scroll when we get a + // send a message to WebCore to re-scroll when we get a // new picture mUserScroll = false; mWebViewCore.sendMessage(EventHub.SYNC_SCROLL, @@ -4707,24 +4956,58 @@ public class WebView extends AbsoluteLayout case SPAWN_SCROLL_TO_MSG_ID: spawnContentScrollTo(msg.arg1, msg.arg2); break; - case NEW_PICTURE_MSG_ID: + case NEW_PICTURE_MSG_ID: { + WebSettings settings = mWebViewCore.getSettings(); // called for new content - final WebViewCore.DrawData draw = + final int viewWidth = getViewWidth(); + final WebViewCore.DrawData draw = (WebViewCore.DrawData) msg.obj; final Point viewSize = draw.mViewPoint; - if (mZoomScale > 0) { - // use the same logic in sendViewSizeZoom() to make sure - // the mZoomScale has matched the viewSize so that we - // can clear mZoomScale - if (Math.round(getViewWidth() / mZoomScale) == viewSize.x) { - mZoomScale = 0; - mWebViewCore.sendMessage(EventHub.SET_SNAP_ANCHOR, - 0, 0); + boolean useWideViewport = settings.getUseWideViewPort(); + WebViewCore.RestoreState restoreState = draw.mRestoreState; + if (restoreState != null) { + mInZoomOverview = false; + mLastScale = restoreState.mTextWrapScale; + if (restoreState.mMinScale == 0) { + if (restoreState.mMobileSite) { + if (draw.mMinPrefWidth > + Math.max(0, draw.mViewPoint.x)) { + mMinZoomScale = (float) viewWidth + / draw.mMinPrefWidth; + mMinZoomScaleFixed = false; + } else { + mMinZoomScale = restoreState.mDefaultScale; + mMinZoomScaleFixed = true; + } + } else { + mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE; + mMinZoomScaleFixed = false; + } + } else { + mMinZoomScale = restoreState.mMinScale; + mMinZoomScaleFixed = true; } - } - if (!mMinZoomScaleFixed) { - mMinZoomScale = (float) getViewWidth() - / Math.max(ZOOM_OUT_WIDTH, draw.mWidthHeight.x); + if (restoreState.mMaxScale == 0) { + mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE; + } else { + mMaxZoomScale = restoreState.mMaxScale; + } + setNewZoomScale(mLastScale, false); + setContentScrollTo(restoreState.mScrollX, + restoreState.mScrollY); + if (useWideViewport + && settings.getLoadWithOverviewMode()) { + if (restoreState.mViewScale == 0 + || (restoreState.mMobileSite + && mMinZoomScale < restoreState.mDefaultScale)) { + mInZoomOverview = true; + } + } + // As we are on a new page, remove the WebTextView. This + // is necessary for page loads driven by webkit, and in + // particular when the user was on a password field, so + // the WebTextView was visible. + clearTextEntry(); } // We update the layout (i.e. request a layout from the // view system) if the last view size that we sent to @@ -4732,125 +5015,88 @@ public class WebView extends AbsoluteLayout // received in the fixed dimension. final boolean updateLayout = viewSize.x == mLastWidthSent && viewSize.y == mLastHeightSent; - recordNewContentSize(draw.mWidthHeight.x, - draw.mWidthHeight.y, updateLayout); - if (LOGV_ENABLED) { + recordNewContentSize(draw.mWidthHeight.x, + draw.mWidthHeight.y + + (mFindIsUp ? mFindHeight : 0), updateLayout); + if (DebugFlags.WEB_VIEW) { Rect b = draw.mInvalRegion.getBounds(); Log.v(LOGTAG, "NEW_PICTURE_MSG_ID {" + b.left+","+b.top+","+b.right+","+b.bottom+"}"); } - invalidate(contentToView(draw.mInvalRegion.getBounds())); + invalidateContentRect(draw.mInvalRegion.getBounds()); if (mPictureListener != null) { mPictureListener.onNewPicture(WebView.this, capturePicture()); } + if (useWideViewport) { + mZoomOverviewWidth = Math.max(draw.mMinPrefWidth, + draw.mViewPoint.x); + } + if (!mMinZoomScaleFixed) { + mMinZoomScale = (float) viewWidth / mZoomOverviewWidth; + } + if (!mDrawHistory && mInZoomOverview) { + // fit the content width to the current view. Ignore + // the rounding error case. + if (Math.abs((viewWidth * mInvActualScale) + - mZoomOverviewWidth) > 1) { + setNewZoomScale((float) viewWidth + / mZoomOverviewWidth, false); + } + } break; + } case WEBCORE_INITIALIZED_MSG_ID: // nativeCreate sets mNativeClass to a non-zero value nativeCreate(msg.arg1); break; case UPDATE_TEXTFIELD_TEXT_MSG_ID: // Make sure that the textfield is currently focused - // and representing the same node as the pointer. - if (inEditingMode() && - mTextEntry.isSameTextField(msg.arg1)) { + // and representing the same node as the pointer. + if (inEditingMode() && + mWebTextView.isSameTextField(msg.arg1)) { if (msg.getData().getBoolean("password")) { - Spannable text = (Spannable) mTextEntry.getText(); + Spannable text = (Spannable) mWebTextView.getText(); int start = Selection.getSelectionStart(text); int end = Selection.getSelectionEnd(text); - mTextEntry.setInPassword(true); + mWebTextView.setInPassword(true); // Restore the selection, which may have been // ruined by setInPassword. - Spannable pword = (Spannable) mTextEntry.getText(); + Spannable pword = + (Spannable) mWebTextView.getText(); Selection.setSelection(pword, start, end); // If the text entry has created more events, ignore // this one. } else if (msg.arg2 == mTextGeneration) { - mTextEntry.setTextAndKeepSelection( + mWebTextView.setTextAndKeepSelection( (String) msg.obj); } } break; - case DID_FIRST_LAYOUT_MSG_ID: - if (mNativeClass == 0) { - break; + case UPDATE_TEXT_SELECTION_MSG_ID: + if (inEditingMode() + && mWebTextView.isSameTextField(msg.arg1) + && msg.arg2 == mTextGeneration) { + WebViewCore.TextSelectionData tData + = (WebViewCore.TextSelectionData) msg.obj; + mWebTextView.setSelectionFromWebKit(tData.mStart, + tData.mEnd); } -// Do not reset the focus or clear the text; the user may have already -// navigated or entered text at this point. The focus should have gotten -// reset, if need be, when the focus cache was built. Similarly, the text -// view should already be torn down and rebuilt if needed. -// nativeResetFocus(); -// clearTextEntry(); - HashMap scaleLimit = (HashMap) msg.obj; - int minScale = (Integer) scaleLimit.get("minScale"); - if (minScale == 0) { - mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE; - mMinZoomScaleFixed = false; - } else { - mMinZoomScale = (float) (minScale / 100.0); - mMinZoomScaleFixed = true; - } - int maxScale = (Integer) scaleLimit.get("maxScale"); - if (maxScale == 0) { - mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE; - } else { - mMaxZoomScale = (float) (maxScale / 100.0); - } - // If history Picture is drawn, don't update zoomWidth - if (mDrawHistory) { - break; - } - int width = getViewWidth(); - if (width == 0) { - break; - } - int initialScale = msg.arg1; - int viewportWidth = msg.arg2; - // start a new page with DEFAULT_SCALE zoom scale. - float scale = mDefaultScale; - if (mInitialScale > 0) { - scale = mInitialScale / 100.0f; - } else { - if (mWebViewCore.getSettings().getUseWideViewPort()) { - // force viewSizeChanged by setting mLastWidthSent - // to 0 - mLastWidthSent = 0; - } - if (initialScale == 0) { - // if viewportWidth is defined and it is smaller - // than the view width, zoom in to fill the view - if (viewportWidth > 0 && viewportWidth < width) { - scale = (float) width / viewportWidth; - } - } else { - scale = initialScale / 100.0f; - } - } - setNewZoomScale(scale, false); break; - case MARK_NODE_INVALID_ID: - nativeMarkNodeInvalid(msg.arg1); - break; - case NOTIFY_FOCUS_SET_MSG_ID: - if (mNativeClass != 0) { - nativeNotifyFocusSet(inEditingMode()); + case MOVE_OUT_OF_PLUGIN: + if (nativePluginEatsNavKey()) { + navHandledKey(msg.arg1, 1, false, 0, true); } break; case UPDATE_TEXT_ENTRY_MSG_ID: - // this is sent after finishing resize in WebViewCore. Make + // this is sent after finishing resize in WebViewCore. Make // sure the text edit box is still on the screen. - boolean alreadyThere = inEditingMode(); - if (alreadyThere && nativeUpdateFocusNode()) { - FocusNode node = mFocusNode; - if (node.mIsTextField || node.mIsTextArea) { - mTextEntry.bringIntoView(); - } + if (inEditingMode() && nativeCursorIsTextInput()) { + mWebTextView.bringIntoView(); + rebuildWebTextView(); } - updateTextEntry(); break; - case RECOMPUTE_FOCUS_MSG_ID: - if (mNativeClass != 0) { - nativeRecomputeFocus(); - } + case CLEAR_TEXT_ENTRY: + clearTextEntry(); break; case INVAL_RECT_MSG_ID: { Rect r = (Rect)msg.obj; @@ -4863,17 +5109,15 @@ public class WebView extends AbsoluteLayout } break; } - case UPDATE_TEXT_ENTRY_ADAPTER: - HashMap data = (HashMap) msg.obj; - if (mTextEntry.isSameTextField(msg.arg1)) { - AutoCompleteAdapter adapter = - (AutoCompleteAdapter) data.get("adapter"); - mTextEntry.setAdapterCustom(adapter); + case REQUEST_FORM_DATA: + AutoCompleteAdapter adapter = (AutoCompleteAdapter) msg.obj; + if (mWebTextView.isSameTextField(msg.arg1)) { + mWebTextView.setAdapterCustom(adapter); } break; case UPDATE_CLIPBOARD: String str = (String) msg.obj; - if (LOGV_ENABLED) { + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "UPDATE_CLIPBOARD " + str); } try { @@ -4888,12 +5132,12 @@ public class WebView extends AbsoluteLayout WebViewCore.resumeUpdate(mWebViewCore); break; - case LONG_PRESS_ENTER: + case LONG_PRESS_CENTER: // as this is shared by keydown and trackballdown, reset all // the states - mGotEnterDown = false; + mGotCenterDown = false; mTrackballDown = false; - // LONG_PRESS_ENTER is sent as a delayed message. If we + // LONG_PRESS_CENTER is sent as a delayed message. If we // switch to windows overview, the WebView will be // temporarily removed from the view system. In that case, // do nothing. @@ -4908,13 +5152,26 @@ public class WebView extends AbsoluteLayout case PREVENT_TOUCH_ID: if (msg.arg1 == MotionEvent.ACTION_DOWN) { - mPreventDrag = msg.arg2 == 1; - if (mPreventDrag) { - mTouchMode = TOUCH_DONE_MODE; + // dont override if mPreventDrag has been set to no due + // to time out + if (mPreventDrag == PREVENT_DRAG_MAYBE_YES) { + mPreventDrag = msg.arg2 == 1 ? PREVENT_DRAG_YES + : PREVENT_DRAG_NO; + if (mPreventDrag == PREVENT_DRAG_YES) { + mTouchMode = TOUCH_DONE_MODE; + } } } break; + case REQUEST_KEYBOARD: + if (msg.arg1 == 0) { + hideSoftKeyboard(); + } else { + displaySoftKeyboard(false); + } + break; + default: super.handleMessage(msg); break; @@ -4924,16 +5181,12 @@ public class WebView extends AbsoluteLayout // Class used to use a dropdown for a <select> element private class InvokeListBox implements Runnable { - // Strings for the labels in the listbox. - private String[] mArray; - // Array representing whether each item is enabled. - private boolean[] mEnableArray; // Whether the listbox allows multiple selection. private boolean mMultiple; // Passed in to a list with multiple selection to tell // which items are selected. private int[] mSelectedArray; - // Passed in to a list with single selection to tell + // Passed in to a list with single selection to tell // where the initial selection is. private int mSelection; @@ -4952,14 +5205,14 @@ public class WebView extends AbsoluteLayout } /** - * Subclass ArrayAdapter so we can disable OptionGroupLabels, + * Subclass ArrayAdapter so we can disable OptionGroupLabels, * and allow filtering. */ private class MyArrayListAdapter extends ArrayAdapter<Container> { public MyArrayListAdapter(Context context, Container[] objects, boolean multiple) { - super(context, + super(context, multiple ? com.android.internal.R.layout.select_dialog_multichoice : - com.android.internal.R.layout.select_dialog_singlechoice, + com.android.internal.R.layout.select_dialog_singlechoice, objects); } @@ -5017,7 +5270,7 @@ public class WebView extends AbsoluteLayout } } - private InvokeListBox(String[] array, boolean[] enabled, int + private InvokeListBox(String[] array, boolean[] enabled, int selection) { mSelection = selection; mMultiple = false; @@ -5080,31 +5333,36 @@ public class WebView extends AbsoluteLayout public void run() { final ListView listView = (ListView) LayoutInflater.from(mContext) .inflate(com.android.internal.R.layout.select_dialog, null); - final MyArrayListAdapter adapter = new + final MyArrayListAdapter adapter = new MyArrayListAdapter(mContext, mContainers, mMultiple); AlertDialog.Builder b = new AlertDialog.Builder(mContext) .setView(listView).setCancelable(true) .setInverseBackgroundForced(true); - + if (mMultiple) { b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { mWebViewCore.sendMessage( - EventHub.LISTBOX_CHOICES, + EventHub.LISTBOX_CHOICES, adapter.getCount(), 0, listView.getCheckedItemPositions()); }}); - b.setNegativeButton(android.R.string.cancel, null); + b.setNegativeButton(android.R.string.cancel, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + mWebViewCore.sendMessage( + EventHub.SINGLE_LISTBOX_CHOICE, -2, 0); + }}); } final AlertDialog dialog = b.create(); listView.setAdapter(adapter); listView.setFocusableInTouchMode(true); // There is a bug (1250103) where the checks in a ListView with // multiple items selected are associated with the positions, not - // the ids, so the items do not properly retain their checks when + // the ids, so the items do not properly retain their checks when // filtered. Do not allow filtering on multiple lists until // that bug is fixed. - + listView.setTextFilterEnabled(!mMultiple); if (mMultiple) { listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); @@ -5167,48 +5425,39 @@ public class WebView extends AbsoluteLayout } // called by JNI - private void sendFinalFocus(int frame, int node, int x, int y) { - WebViewCore.FocusData focusData = new WebViewCore.FocusData(); - focusData.mFrame = frame; - focusData.mNode = node; - focusData.mX = x; - focusData.mY = y; - mWebViewCore.sendMessage(EventHub.SET_FINAL_FOCUS, - EventHub.NO_FOCUS_CHANGE_BLOCK, 0, focusData); + private void sendMoveMouse(int frame, int node, int x, int y) { + mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, + new WebViewCore.CursorData(frame, node, x, y)); } - // called by JNI - private void setFocusData(int moveGeneration, int buildGeneration, - int frame, int node, int x, int y, boolean ignoreNullFocus) { - mFocusData.mMoveGeneration = moveGeneration; - mFocusData.mBuildGeneration = buildGeneration; - mFocusData.mFrame = frame; - mFocusData.mNode = node; - mFocusData.mX = x; - mFocusData.mY = y; - mFocusData.mIgnoreNullFocus = ignoreNullFocus; - } - - // called by JNI - private void sendKitFocus() { - WebViewCore.FocusData focusData = new WebViewCore.FocusData(mFocusData); - mWebViewCore.sendMessage(EventHub.SET_KIT_FOCUS, focusData); + /* + * Send a mouse move event to the webcore thread. + * + * @param removeFocus Pass true if the "mouse" cursor is now over a node + * which wants key events, but it is not the focus. This + * will make the visual appear as though nothing is in + * focus. Remove the WebTextView, if present, and stop + * drawing the blinking caret. + * called by JNI + */ + private void sendMoveMouseIfLatest(boolean removeFocus) { + if (removeFocus) { + clearTextEntry(); + setFocusControllerInactive(); + } + mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE_IF_LATEST, + cursorData()); } // called by JNI - private void sendMotionUp(int touchGeneration, int buildGeneration, - int frame, int node, int x, int y, int size, boolean isClick, - boolean retry) { + private void sendMotionUp(int touchGeneration, + int frame, int node, int x, int y) { WebViewCore.TouchUpData touchUpData = new WebViewCore.TouchUpData(); touchUpData.mMoveGeneration = touchGeneration; - touchUpData.mBuildGeneration = buildGeneration; - touchUpData.mSize = size; - touchUpData.mIsClick = isClick; - touchUpData.mRetry = retry; - mFocusData.mFrame = touchUpData.mFrame = frame; - mFocusData.mNode = touchUpData.mNode = node; - mFocusData.mX = touchUpData.mX = x; - mFocusData.mY = touchUpData.mY = y; + touchUpData.mFrame = frame; + touchUpData.mNode = node; + touchUpData.mX = x; + touchUpData.mY = y; mWebViewCore.sendMessage(EventHub.TOUCH_UP, touchUpData); } @@ -5223,7 +5472,7 @@ public class WebView extends AbsoluteLayout width = visRect.width() / 2; } // FIXME the divisor should be retrieved from somewhere - return viewToContent(width); + return viewToContentX(width); } private int getScaledMaxYScroll() { @@ -5238,7 +5487,7 @@ public class WebView extends AbsoluteLayout // FIXME the divisor should be retrieved from somewhere // the closest thing today is hard-coded into ScrollView.java // (from ScrollView.java, line 363) int maxJump = height/2; - return viewToContent(height); + return Math.round(height * mInvActualScale); } /** @@ -5247,56 +5496,72 @@ public class WebView extends AbsoluteLayout private void viewInvalidate() { invalidate(); } - + // return true if the key was handled - private boolean navHandledKey(int keyCode, int count, boolean noScroll - , long time) { + private boolean navHandledKey(int keyCode, int count, boolean noScroll, + long time, boolean ignorePlugin) { if (mNativeClass == 0) { return false; } - mLastFocusTime = time; - mLastFocusBounds = nativeGetFocusRingBounds(); - boolean keyHandled = nativeMoveFocus(keyCode, count, noScroll) == false; - if (LOGV_ENABLED) { - Log.v(LOGTAG, "navHandledKey mLastFocusBounds=" + mLastFocusBounds - + " mLastFocusTime=" + mLastFocusTime + if (ignorePlugin == false && nativePluginEatsNavKey()) { + KeyEvent event = new KeyEvent(time, time, KeyEvent.ACTION_DOWN + , keyCode, count, (mShiftIsPressed ? KeyEvent.META_SHIFT_ON : 0) + | (false ? KeyEvent.META_ALT_ON : 0) // FIXME + | (false ? KeyEvent.META_SYM_ON : 0) // FIXME + , 0, 0, 0); + mWebViewCore.sendMessage(EventHub.KEY_DOWN, event); + mWebViewCore.sendMessage(EventHub.KEY_UP, event); + return true; + } + mLastCursorTime = time; + mLastCursorBounds = nativeGetCursorRingBounds(); + boolean keyHandled + = nativeMoveCursor(keyCode, count, noScroll) == false; + if (DebugFlags.WEB_VIEW) { + Log.v(LOGTAG, "navHandledKey mLastCursorBounds=" + mLastCursorBounds + + " mLastCursorTime=" + mLastCursorTime + " handled=" + keyHandled); } if (keyHandled == false || mHeightCanMeasure == false) { return keyHandled; } - Rect contentFocus = nativeGetFocusRingBounds(); - if (contentFocus.isEmpty()) return keyHandled; - Rect viewFocus = contentToView(contentFocus); + Rect contentCursorRingBounds = nativeGetCursorRingBounds(); + if (contentCursorRingBounds.isEmpty()) return keyHandled; + Rect viewCursorRingBounds = contentToViewRect(contentCursorRingBounds); Rect visRect = new Rect(); calcOurVisibleRect(visRect); Rect outset = new Rect(visRect); int maxXScroll = visRect.width() / 2; int maxYScroll = visRect.height() / 2; outset.inset(-maxXScroll, -maxYScroll); - if (Rect.intersects(outset, viewFocus) == false) { + if (Rect.intersects(outset, viewCursorRingBounds) == false) { return keyHandled; } // FIXME: Necessary because ScrollView/ListView do not scroll left/right - int maxH = Math.min(viewFocus.right - visRect.right, maxXScroll); + int maxH = Math.min(viewCursorRingBounds.right - visRect.right, + maxXScroll); if (maxH > 0) { pinScrollBy(maxH, 0, true, 0); } else { - maxH = Math.max(viewFocus.left - visRect.left, -maxXScroll); + maxH = Math.max(viewCursorRingBounds.left - visRect.left, + -maxXScroll); if (maxH < 0) { pinScrollBy(maxH, 0, true, 0); } } - if (mLastFocusBounds.isEmpty()) return keyHandled; - if (mLastFocusBounds.equals(contentFocus)) return keyHandled; - if (LOGV_ENABLED) { - Log.v(LOGTAG, "navHandledKey contentFocus=" + contentFocus); + if (mLastCursorBounds.isEmpty()) return keyHandled; + if (mLastCursorBounds.equals(contentCursorRingBounds)) { + return keyHandled; } - requestRectangleOnScreen(viewFocus); + if (DebugFlags.WEB_VIEW) { + Log.v(LOGTAG, "navHandledKey contentCursorRingBounds=" + + contentCursorRingBounds); + } + requestRectangleOnScreen(viewCursorRingBounds); mUserScroll = true; return keyHandled; } - + /** * Set the background color. It's white by default. Pass * zero to make the view transparent. @@ -5311,7 +5576,7 @@ public class WebView extends AbsoluteLayout nativeDebugDump(); mWebViewCore.sendMessage(EventHub.DUMP_NAVTREE); } - + /** * Update our cache with updatedText. * @param updatedText The new text to put in our cache. @@ -5321,52 +5586,85 @@ public class WebView extends AbsoluteLayout // we recognize that it is up to date. nativeUpdateCachedTextfield(updatedText, mTextGeneration); } - - // Never call this version except by updateCachedTextfield(String) - - // we always want to pass in our generation number. - private native void nativeUpdateCachedTextfield(String updatedText, - int generation); - private native void nativeClearFocus(int x, int y); + + /* package */ native void nativeClearCursor(); private native void nativeCreate(int ptr); + private native int nativeCursorFramePointer(); + private native Rect nativeCursorNodeBounds(); + /* package */ native int nativeCursorNodePointer(); + /* package */ native boolean nativeCursorMatchesFocus(); + private native boolean nativeCursorIntersects(Rect visibleRect); + private native boolean nativeCursorIsAnchor(); + private native boolean nativeCursorIsPlugin(); + private native boolean nativeCursorIsTextInput(); + private native Point nativeCursorPosition(); + private native String nativeCursorText(); + /** + * Returns true if the native cursor node says it wants to handle key events + * (ala plugins). This can only be called if mNativeClass is non-zero! + */ + private native boolean nativeCursorWantsKeyEvents(); private native void nativeDebugDump(); private native void nativeDestroy(); - private native void nativeDrawFocusRing(Canvas content); - private native void nativeDrawSelection(Canvas content - , int x, int y, boolean extendSelection); + private native void nativeDrawCursorRing(Canvas content); + private native void nativeDrawMatches(Canvas canvas); + private native void nativeDrawSelection(Canvas content, float scale, + int offset, int x, int y, boolean extendSelection); private native void nativeDrawSelectionRegion(Canvas content); - private native boolean nativeUpdateFocusNode(); - private native Rect nativeGetFocusRingBounds(); - private native Rect nativeGetNavBounds(); + private native void nativeDumpDisplayTree(String urlOrNull); + private native int nativeFindAll(String findLower, String findUpper); + private native void nativeFindNext(boolean forward); + private native boolean nativeFocusCandidateIsPassword(); + private native boolean nativeFocusCandidateIsRtlText(); + private native boolean nativeFocusCandidateIsTextField(); + private native boolean nativeFocusCandidateIsTextInput(); + private native int nativeFocusCandidateMaxLength(); + /* package */ native String nativeFocusCandidateName(); + private native Rect nativeFocusCandidateNodeBounds(); + /* package */ native int nativeFocusCandidatePointer(); + private native String nativeFocusCandidateText(); + private native int nativeFocusCandidateTextSize(); + /* package */ native int nativeFocusNodePointer(); + private native Rect nativeGetCursorRingBounds(); + private native Region nativeGetSelection(); + private native boolean nativeHasCursorNode(); + private native boolean nativeHasFocusNode(); + private native void nativeHideCursor(); + private native String nativeImageURI(int x, int y); private native void nativeInstrumentReport(); - private native void nativeMarkNodeInvalid(int node); + /* package */ native void nativeMoveCursorToNextTextInput(); // return true if the page has been scrolled - private native boolean nativeMotionUp(int x, int y, int slop, boolean isClick); + private native boolean nativeMotionUp(int x, int y, int slop); // returns false if it handled the key - private native boolean nativeMoveFocus(int keyCode, int count, + private native boolean nativeMoveCursor(int keyCode, int count, boolean noScroll); - private native void nativeNotifyFocusSet(boolean inEditingMode); - private native void nativeRecomputeFocus(); + private native int nativeMoveGeneration(); + private native void nativeMoveSelection(int x, int y, + boolean extendSelection); + private native boolean nativePluginEatsNavKey(); // Like many other of our native methods, you must make sure that // mNativeClass is not null before calling this method. private native void nativeRecordButtons(boolean focused, boolean pressed, boolean invalidate); - private native void nativeResetFocus(); - private native void nativeResetNavClipBounds(); private native void nativeSelectBestAt(Rect rect); private native void nativeSetFindIsDown(); private native void nativeSetFollowedLink(boolean followed); private native void nativeSetHeightCanMeasure(boolean measure); - private native void nativeSetNavBounds(Rect rect); - private native void nativeSetNavClipBounds(Rect rect); - private native String nativeImageURI(int x, int y); + // Returns a value corresponding to CachedFrame::ImeAction + /* package */ native int nativeTextFieldAction(); /** - * Returns true if the native focus nodes says it wants to handle key events - * (ala plugins). This can only be called if mNativeClass is non-zero! + * Perform a click on a currently focused text input. Since it is already + * focused, there is no need to go through the nativeMotionUp code, which + * may change the Cursor. */ - private native boolean nativeFocusNodeWantsKeyEvents(); - private native void nativeMoveSelection(int x, int y - , boolean extendSelection); - private native Region nativeGetSelection(); - - private native void nativeDumpDisplayTree(String urlOrNull); + private native void nativeTextInputMotionUp(int x, int y); + private native int nativeTextGeneration(); + // Never call this version except by updateCachedTextfield(String) - + // we always want to pass in our generation number. + private native void nativeUpdateCachedTextfield(String updatedText, + int generation); + private native void nativeUpdatePluginReceivesEvents(); + // return NO_LEFTEDGE means failure. + private static final int NO_LEFTEDGE = -1; + private native int nativeGetBlockLeftEdge(int x, int y, float scale); } |