summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--content/browser/android/content_view_core_impl.cc41
-rw-r--r--content/browser/android/content_view_core_impl.h9
-rw-r--r--content/browser/renderer_host/render_widget_host_view_android.cc30
-rw-r--r--content/browser/renderer_host/render_widget_host_view_android.h5
-rw-r--r--content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java159
-rw-r--r--content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java.orig2078
-rw-r--r--content/public/android/java/src/org/chromium/content/browser/CursorController.java41
-rw-r--r--content/public/android/java/src/org/chromium/content/browser/HandleView.java370
-rw-r--r--content/public/android/java/src/org/chromium/content/browser/InsertionHandleController.java270
-rw-r--r--content/public/android/java/src/org/chromium/content/browser/SelectionHandleController.java182
10 files changed, 3164 insertions, 21 deletions
diff --git a/content/browser/android/content_view_core_impl.cc b/content/browser/android/content_view_core_impl.cc
index d1cf353..a3b39a2 100644
--- a/content/browser/android/content_view_core_impl.cc
+++ b/content/browser/android/content_view_core_impl.cc
@@ -72,7 +72,8 @@ jfieldID g_native_content_view;
namespace content {
struct ContentViewCoreImpl::JavaObject {
-
+ ScopedJavaGlobalRef<jclass> rect_clazz;
+ jmethodID rect_constructor;
};
ContentViewCore* ContentViewCore::GetNativeContentViewCore(JNIEnv* env,
@@ -173,6 +174,9 @@ void ContentViewCoreImpl::Observe(int type,
void ContentViewCoreImpl::InitJNI(JNIEnv* env, jobject obj) {
java_object_ = new JavaObject;
+ java_object_->rect_clazz.Reset(GetClass(env, "android/graphics/Rect"));
+ java_object_->rect_constructor =
+ GetMethodID(env, java_object_->rect_clazz, "<init>", "(IIII)V");
}
RenderWidgetHostViewAndroid*
@@ -339,7 +343,40 @@ bool ContentViewCoreImpl::HasFocus() {
}
void ContentViewCoreImpl::OnSelectionChanged(const std::string& text) {
- NOTIMPLEMENTED() << "not upstreamed yet";
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
+ if (obj.is_null())
+ return;
+ ScopedJavaLocalRef<jstring> jtext = ConvertUTF8ToJavaString(env, text);
+ Java_ContentViewCore_onSelectionChanged(env, obj.obj(), jtext.obj());
+}
+
+void ContentViewCoreImpl::OnSelectionBoundsChanged(
+ const gfx::Rect& start_rect, base::i18n::TextDirection start_dir,
+ const gfx::Rect& end_rect, base::i18n::TextDirection end_dir) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
+ if (obj.is_null())
+ return;
+ ScopedJavaLocalRef<jobject> start_rect_object(env,
+ env->NewObject(java_object_->rect_clazz.obj(),
+ java_object_->rect_constructor,
+ start_rect.x(),
+ start_rect.y(),
+ start_rect.right(),
+ start_rect.bottom()));
+ ScopedJavaLocalRef<jobject> end_rect_object(env,
+ env->NewObject(java_object_->rect_clazz.obj(),
+ java_object_->rect_constructor,
+ end_rect.x(),
+ end_rect.y(),
+ end_rect.right(),
+ end_rect.bottom()));
+ Java_ContentViewCore_onSelectionBoundsChanged(env, obj.obj(),
+ start_rect_object.obj(),
+ start_dir,
+ end_rect_object.obj(),
+ end_dir);
}
void ContentViewCoreImpl::StartContentIntent(const GURL& content_url) {
diff --git a/content/browser/android/content_view_core_impl.h b/content/browser/android/content_view_core_impl.h
index 4a60213..99b0625 100644
--- a/content/browser/android/content_view_core_impl.h
+++ b/content/browser/android/content_view_core_impl.h
@@ -196,12 +196,9 @@ class ContentViewCoreImpl : public ContentViewCore,
void ConfirmTouchEvent(bool handled);
void DidSetNeedTouchEvents(bool need_touch_events);
void OnSelectionChanged(const std::string& text);
- void OnSelectionBoundsChanged(int startx,
- int starty,
- base::i18n::TextDirection start_dir,
- int endx,
- int endy,
- base::i18n::TextDirection end_dir);
+ void OnSelectionBoundsChanged(
+ const gfx::Rect& start_rect, base::i18n::TextDirection start_dir,
+ const gfx::Rect& end_rect, base::i18n::TextDirection end_dir);
void StartContentIntent(const GURL& content_url);
diff --git a/content/browser/renderer_host/render_widget_host_view_android.cc b/content/browser/renderer_host/render_widget_host_view_android.cc
index 333b34e..1248e91 100644
--- a/content/browser/renderer_host/render_widget_host_view_android.cc
+++ b/content/browser/renderer_host/render_widget_host_view_android.cc
@@ -21,6 +21,22 @@
namespace content {
+namespace {
+
+// TODO(pliard): http://crbug.com/142585. Remove this helper function and update
+// the clients to deal directly with WebKit::WebTextDirection.
+base::i18n::TextDirection ConvertTextDirection(WebKit::WebTextDirection dir) {
+ switch (dir) {
+ case WebKit::WebTextDirectionDefault: return base::i18n::UNKNOWN_DIRECTION;
+ case WebKit::WebTextDirectionLeftToRight: return base::i18n::LEFT_TO_RIGHT;
+ case WebKit::WebTextDirectionRightToLeft: return base::i18n::RIGHT_TO_LEFT;
+ }
+ NOTREACHED() << "Unsupported text direction " << dir;
+ return base::i18n::UNKNOWN_DIRECTION;
+}
+
+} // namespace
+
RenderWidgetHostViewAndroid::RenderWidgetHostViewAndroid(
RenderWidgetHostImpl* widget_host,
ContentViewCoreImpl* content_view_core)
@@ -251,6 +267,20 @@ void RenderWidgetHostViewAndroid::SelectionChanged(const string16& text,
content_view_core_->OnSelectionChanged(utf8_selection);
}
+void RenderWidgetHostViewAndroid::SelectionBoundsChanged(
+ const gfx::Rect& start_rect,
+ WebKit::WebTextDirection start_direction,
+ const gfx::Rect& end_rect,
+ WebKit::WebTextDirection end_direction) {
+ if (content_view_core_) {
+ content_view_core_->OnSelectionBoundsChanged(
+ start_rect,
+ ConvertTextDirection(start_direction),
+ end_rect,
+ ConvertTextDirection(end_direction));
+ }
+}
+
BackingStore* RenderWidgetHostViewAndroid::AllocBackingStore(
const gfx::Size& size) {
NOTIMPLEMENTED();
diff --git a/content/browser/renderer_host/render_widget_host_view_android.h b/content/browser/renderer_host/render_widget_host_view_android.h
index 31ae5b2..6f36e24 100644
--- a/content/browser/renderer_host/render_widget_host_view_android.h
+++ b/content/browser/renderer_host/render_widget_host_view_android.h
@@ -80,6 +80,11 @@ class RenderWidgetHostViewAndroid : public RenderWidgetHostViewBase {
virtual void SelectionChanged(const string16& text,
size_t offset,
const ui::Range& range) OVERRIDE;
+ virtual void SelectionBoundsChanged(
+ const gfx::Rect& start_rect,
+ WebKit::WebTextDirection start_direction,
+ const gfx::Rect& end_rect,
+ WebKit::WebTextDirection end_direction) OVERRIDE;
virtual void OnAcceleratedCompositingStateChange() OVERRIDE;
virtual void AcceleratedSurfaceBuffersSwapped(
const GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params& params,
diff --git a/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java b/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java
index f6b9d8d..0ad997d 100644
--- a/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java
+++ b/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java
@@ -197,13 +197,21 @@ public class ContentViewCore implements MotionEventDelegate {
private SelectionHandleController mSelectionHandleController;
private InsertionHandleController mInsertionHandleController;
+ // These offsets in document space with page scale normalized to 1.0.
+ private final PointF mStartHandleNormalizedPoint = new PointF();
+ private final PointF mEndHandleNormalizedPoint = new PointF();
+ private final PointF mInsertionHandleNormalizedPoint = new PointF();
// Tracks whether a selection is currently active. When applied to selected text, indicates
// whether the last selected text is still highlighted.
private boolean mHasSelection;
private String mLastSelectedText;
private boolean mSelectionEditable;
- private ActionMode mActionMode;
+ // TODO(http://code.google.com/p/chromium/issues/detail?id=136704): Register
+ // the necessary resources in ContentViewActivity so we can create an action
+ // bar for text editing.
+ // private ActionMode mActionMode;
+ private boolean mActionBarVisible; // Remove this when mActionMode is upstreamed.
// The legacy webview DownloadListener.
private DownloadListener mDownloadListener;
@@ -982,8 +990,10 @@ public class ContentViewCore implements MotionEventDelegate {
}
void hideSelectActionBar() {
- if (mActionMode != null) {
- mActionMode.finish();
+ if (mActionBarVisible) {
+ mActionBarVisible = false;
+ mImeAdapter.unselect();
+ getContentViewClient().onContextualActionBarHidden();
}
}
@@ -1335,6 +1345,8 @@ public class ContentViewCore implements MotionEventDelegate {
if (!mPopupZoomer.isShowing()) mPopupZoomer.setLastTouch(x, y);
if (isLongPress) {
+ getInsertionHandleController().allowAutomaticShowing();
+ getSelectionHandleController().allowAutomaticShowing();
if (mNativeContentViewCore != 0) {
nativeLongPress(mNativeContentViewCore, timeMs, x, y, false);
}
@@ -1425,8 +1437,28 @@ public class ContentViewCore implements MotionEventDelegate {
private SelectionHandleController getSelectionHandleController() {
if (mSelectionHandleController == null) {
- mSelectionHandleController = new SelectionHandleController(getContainerView());
- // TODO(olilan): add specific method implementations.
+ mSelectionHandleController = new SelectionHandleController(getContainerView()) {
+ @Override
+ public void selectBetweenCoordinates(int x1, int y1, int x2, int y2) {
+ if (mNativeContentViewCore != 0 && !(x1 == x2 && y1 == y2)) {
+ nativeSelectBetweenCoordinates(mNativeContentViewCore, x1, y1, x2, y2);
+ }
+ }
+
+ @Override
+ public void showHandlesAt(int x1, int y1, int dir1, int x2, int y2, int dir2) {
+ super.showHandlesAt(x1, y1, dir1, x2, y2, dir2);
+ mStartHandleNormalizedPoint.set(
+ (x1 + mNativeScrollX) / mNativePageScaleFactor,
+ (y1 + mNativeScrollY) / mNativePageScaleFactor);
+ mEndHandleNormalizedPoint.set(
+ (x2 + mNativeScrollX) / mNativePageScaleFactor,
+ (y2 + mNativeScrollY) / mNativePageScaleFactor);
+
+ showSelectActionBar();
+ }
+
+ };
mSelectionHandleController.hideAndDisallowAutomaticShowing();
}
@@ -1436,8 +1468,35 @@ public class ContentViewCore implements MotionEventDelegate {
private InsertionHandleController getInsertionHandleController() {
if (mInsertionHandleController == null) {
- mInsertionHandleController = new InsertionHandleController(getContainerView());
- // TODO(olilan): add specific method implementations.
+ mInsertionHandleController = new InsertionHandleController(getContainerView()) {
+ private static final int AVERAGE_LINE_HEIGHT = 14;
+
+ @Override
+ public void setCursorPosition(int x, int y) {
+ if (mNativeContentViewCore != 0) {
+ nativeSelectBetweenCoordinates(mNativeContentViewCore, x, y, x, y);
+ }
+ }
+
+ @Override
+ public void paste() {
+ mImeAdapter.paste();
+ hideHandles();
+ }
+
+ @Override
+ public int getLineHeight() {
+ return (int) (mNativePageScaleFactor * AVERAGE_LINE_HEIGHT);
+ }
+
+ @Override
+ public void showHandleAt(int x, int y) {
+ super.showHandleAt(x, y);
+ mInsertionHandleNormalizedPoint.set(
+ (x + mNativeScrollX) / mNativePageScaleFactor,
+ (y + mNativeScrollY) / mNativePageScaleFactor);
+ }
+ };
mInsertionHandleController.hideAndDisallowAutomaticShowing();
}
@@ -1445,7 +1504,42 @@ public class ContentViewCore implements MotionEventDelegate {
return mInsertionHandleController;
}
+ private void updateHandleScreenPositions() {
+ if (mSelectionHandleController != null && mSelectionHandleController.isShowing()) {
+ float startX = mStartHandleNormalizedPoint.x * mNativePageScaleFactor - mNativeScrollX;
+ float startY = mStartHandleNormalizedPoint.y * mNativePageScaleFactor - mNativeScrollY;
+ mSelectionHandleController.setStartHandlePosition((int) startX, (int) startY);
+
+ float endX = mEndHandleNormalizedPoint.x * mNativePageScaleFactor - mNativeScrollX;
+ float endY = mEndHandleNormalizedPoint.y * mNativePageScaleFactor - mNativeScrollY;
+ mSelectionHandleController.setEndHandlePosition((int) endX, (int) endY);
+ }
+
+ if (mInsertionHandleController != null && mInsertionHandleController.isShowing()) {
+ float x = mInsertionHandleNormalizedPoint.x * mNativePageScaleFactor - mNativeScrollX;
+ float y = mInsertionHandleNormalizedPoint.y * mNativePageScaleFactor - mNativeScrollY;
+ mInsertionHandleController.setHandlePosition((int) x, (int) y);
+ }
+ }
+
+ private void hideHandles() {
+ if (mSelectionHandleController != null) {
+ mSelectionHandleController.hideAndDisallowAutomaticShowing();
+ }
+ if (mInsertionHandleController != null) {
+ mInsertionHandleController.hideAndDisallowAutomaticShowing();
+ }
+ }
+
private void showSelectActionBar() {
+ if (!mActionBarVisible) {
+ mActionBarVisible = true;
+ getContentViewClient().onContextualActionBarShown();
+ }
+
+// TODO(http://code.google.com/p/chromium/issues/detail?id=136704): Uncomment
+// this code when we have the resources needed to create the action bar.
+/*
if (mActionMode != null) {
mActionMode.invalidate();
return;
@@ -1500,6 +1594,7 @@ public class ContentViewCore implements MotionEventDelegate {
} else {
getContentViewClient().onContextualActionBarShown();
}
+*/
}
public boolean getUseDesktopUserAgent() {
@@ -1616,6 +1711,56 @@ public class ContentViewCore implements MotionEventDelegate {
@SuppressWarnings("unused")
@CalledByNative
+ private void onSelectionChanged(String text) {
+ mLastSelectedText = text;
+ }
+
+ @SuppressWarnings("unused")
+ @CalledByNative
+ private void onSelectionBoundsChanged(Rect startRect, int dir1, Rect endRect, int dir2) {
+ int x1 = startRect.left;
+ int y1 = startRect.bottom;
+ int x2 = endRect.left;
+ int y2 = endRect.bottom;
+ if (x1 != x2 || y1 != y2) {
+ if (mInsertionHandleController != null) {
+ mInsertionHandleController.hide();
+ }
+ getSelectionHandleController().onSelectionChanged(x1, y1, dir1, x2, y2, dir2);
+ mHasSelection = true;
+ } else {
+ hideSelectActionBar();
+ if (x1 != 0 && y1 != 0
+ && (mSelectionHandleController == null
+ || !mSelectionHandleController.isDragging())
+ && mSelectionEditable) {
+ // Selection is a caret, and a text field is focused.
+ if (mSelectionHandleController != null) {
+ mSelectionHandleController.hide();
+ }
+ getInsertionHandleController().onCursorPositionChanged(x1, y1);
+ InputMethodManager manager = (InputMethodManager)
+ getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ if (manager.isWatchingCursor(mContainerView)) {
+ manager.updateCursor(mContainerView, startRect.left, startRect.top,
+ startRect.right, startRect.bottom);
+ }
+ } else {
+ // Deselection
+ if (mSelectionHandleController != null
+ && !mSelectionHandleController.isDragging()) {
+ mSelectionHandleController.hideAndDisallowAutomaticShowing();
+ }
+ if (mInsertionHandleController != null) {
+ mInsertionHandleController.hideAndDisallowAutomaticShowing();
+ }
+ }
+ mHasSelection = false;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ @CalledByNative
private void onEvaluateJavaScriptResult(int id, String jsonResult) {
getContentViewClient().onEvaluateJavaScriptResult(id, jsonResult);
}
diff --git a/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java.orig b/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java.orig
new file mode 100644
index 0000000..f6b9d8d
--- /dev/null
+++ b/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java.orig
@@ -0,0 +1,2078 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.content.browser;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.ResultReceiver;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.ActionMode;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
+import android.webkit.DownloadListener;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+import org.chromium.base.WeakContext;
+import org.chromium.content.app.AppResource;
+import org.chromium.content.browser.ContentViewGestureHandler.MotionEventDelegate;
+import org.chromium.content.browser.accessibility.AccessibilityInjector;
+import org.chromium.content.common.CleanupReference;
+import org.chromium.content.common.TraceEvent;
+import org.chromium.ui.gfx.NativeWindow;
+
+/**
+ * Provides a Java-side 'wrapper' around a WebContent (native) instance.
+ * Contains all the major functionality necessary to manage the lifecycle of a ContentView without
+ * being tied to the view system.
+ */
+@JNINamespace("content")
+public class ContentViewCore implements MotionEventDelegate {
+ private static final String TAG = ContentViewCore.class.getName();
+
+ // The following constants match the ones in chrome/common/page_transition_types.h.
+ // Add more if you need them.
+ public static final int PAGE_TRANSITION_LINK = 0;
+ public static final int PAGE_TRANSITION_TYPED = 1;
+ public static final int PAGE_TRANSITION_AUTO_BOOKMARK = 2;
+ public static final int PAGE_TRANSITION_START_PAGE = 6;
+
+ // Used when ContentView implements a standalone View.
+ public static final int PERSONALITY_VIEW = 0;
+ // Used for Chrome.
+ public static final int PERSONALITY_CHROME = 1;
+
+ // Used to avoid enabling zooming in / out if resulting zooming will
+ // produce little visible difference.
+ private static final float ZOOM_CONTROLS_EPSILON = 0.007f;
+
+ // To avoid checkerboard, we clamp the fling velocity based on the maximum number of tiles
+ // should be allowed to upload per 100ms.
+ private final int mMaxNumUploadTiles = 12;
+
+ // Personality of the ContentView.
+ private final int mPersonality;
+
+ /**
+ * Interface that consumers of {@link ContentViewCore} must implement to allow the proper
+ * dispatching of view methods through the containing view.
+ *
+ * <p>
+ * All methods with the "super_" prefix should be routed to the parent of the
+ * implementing container view.
+ */
+ @SuppressWarnings("javadoc")
+ public static interface InternalAccessDelegate {
+ /**
+ * @see View#drawChild(Canvas, View, long)
+ */
+ boolean drawChild(Canvas canvas, View child, long drawingTime);
+
+ /**
+ * @see View#onKeyUp(keyCode, KeyEvent)
+ */
+ boolean super_onKeyUp(int keyCode, KeyEvent event);
+
+ /**
+ * @see View#dispatchKeyEventPreIme(KeyEvent)
+ */
+ boolean super_dispatchKeyEventPreIme(KeyEvent event);
+
+ /**
+ * @see View#dispatchKeyEvent(KeyEvent)
+ */
+ boolean super_dispatchKeyEvent(KeyEvent event);
+
+ /**
+ * @see View#onGenericMotionEvent(MotionEvent)
+ */
+ boolean super_onGenericMotionEvent(MotionEvent event);
+
+ /**
+ * @see View#onConfigurationChanged(Configuration)
+ */
+ void super_onConfigurationChanged(Configuration newConfig);
+
+ /**
+ * @see View#onScrollChanged(int, int, int, int)
+ */
+ void onScrollChanged(int l, int t, int oldl, int oldt);
+
+ /**
+ * @see View#awakenScrollBars()
+ */
+ boolean awakenScrollBars();
+
+ /**
+ * @see View#awakenScrollBars(int, boolean)
+ */
+ boolean super_awakenScrollBars(int startDelay, boolean invalidate);
+ }
+
+ private static final class DestroyRunnable implements Runnable {
+ private final int mNativeContentViewCore;
+ private DestroyRunnable(int nativeContentViewCore) {
+ mNativeContentViewCore = nativeContentViewCore;
+ }
+ @Override
+ public void run() {
+ nativeDestroy(mNativeContentViewCore);
+ }
+ }
+
+ private CleanupReference mCleanupReference;
+
+ private final Context mContext;
+ private ViewGroup mContainerView;
+ private InternalAccessDelegate mContainerViewInternals;
+ private WebContentsObserverAndroid mWebContentsObserver;
+
+ private ContentViewClient mContentViewClient;
+
+ private ContentSettings mContentSettings;
+
+ // Native pointer to C++ ContentViewCoreImpl object which will be set by nativeInit().
+ private int mNativeContentViewCore = 0;
+
+ // The vsync monitor is used to lock the compositor to the display refresh rate.
+ private VSyncMonitor mVSyncMonitor;
+
+ // The VSyncMonitor gives the timebase for the actual vsync, but we don't want render until
+ // we have had a chance for input events to propagate to the compositor thread. This takes
+ // 3 ms typically, so we adjust the vsync timestamps forward by a bit to give input events a
+ // chance to arrive.
+ private static final long INPUT_EVENT_LAG_FROM_VSYNC_MICROSECONDS = 3200;
+
+ private ContentViewGestureHandler mContentViewGestureHandler;
+ private ZoomManager mZoomManager;
+
+ // Currently ContentView's scrolling is handled by the native side. We keep a cached copy of the
+ // scroll offset and content size so that we can display the scrollbar correctly. In the future,
+ // we may switch to tile rendering and handle the scrolling in the Java level.
+
+ // Cached scroll offset from the native
+ private int mNativeScrollX;
+ private int mNativeScrollY;
+
+ // Cached content size from the native
+ private int mContentWidth;
+ private int mContentHeight;
+
+ // Cached page scale factor from native
+ private float mNativePageScaleFactor = 1.0f;
+ private float mNativeMinimumScale = 1.0f;
+ private float mNativeMaximumScale = 1.0f;
+
+ private PopupZoomer mPopupZoomer;
+
+ private Runnable mFakeMouseMoveRunnable = null;
+
+ // Only valid when focused on a text / password field.
+ private ImeAdapter mImeAdapter;
+ private ImeAdapter.AdapterInputConnection mInputConnection;
+
+ private SelectionHandleController mSelectionHandleController;
+ private InsertionHandleController mInsertionHandleController;
+
+ // Tracks whether a selection is currently active. When applied to selected text, indicates
+ // whether the last selected text is still highlighted.
+ private boolean mHasSelection;
+ private String mLastSelectedText;
+ private boolean mSelectionEditable;
+ private ActionMode mActionMode;
+
+ // The legacy webview DownloadListener.
+ private DownloadListener mDownloadListener;
+ // ContentViewDownloadDelegate adds support for authenticated downloads
+ // and POST downloads. Embedders should prefer ContentViewDownloadDelegate
+ // over DownloadListener.
+ private ContentViewDownloadDelegate mDownloadDelegate;
+
+ // Whether a physical keyboard is connected.
+ private boolean mKeyboardConnected;
+
+ private final VSyncMonitor.Listener mVSyncListener = new VSyncMonitor.Listener() {
+ @Override
+ public void onVSync(VSyncMonitor monitor, long vsyncTimeMicros) {
+ TraceEvent.instant("VSync");
+ if (mNativeContentViewCore == 0 || mVSyncMonitor == null) {
+ return;
+ }
+ // Compensate for input event lag. Input events are delivered immediately on pre-JB
+ // releases, so this adjustment is only done for later versions.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ vsyncTimeMicros += INPUT_EVENT_LAG_FROM_VSYNC_MICROSECONDS;
+ }
+ nativeUpdateVSyncParameters(mNativeContentViewCore, vsyncTimeMicros,
+ mVSyncMonitor.getVSyncPeriodInMicroseconds());
+ }
+ };
+
+ // The AccessibilityInjector that handles loading Accessibility scripts into the web page.
+ private AccessibilityInjector mAccessibilityInjector;
+
+ // Temporary notification to tell onSizeChanged to focus a form element,
+ // because the OSK was just brought up.
+ private boolean mFocusOnNextSizeChanged = false;
+ private boolean mUnfocusOnNextSizeChanged = false;
+
+ private boolean mNeedUpdateOrientationChanged;
+
+ // Used to keep track of whether we should try to undo the last zoom-to-textfield operation.
+ private boolean mScrolledAndZoomedFocusedEditableNode = false;
+
+ // Whether we use hardware-accelerated drawing.
+ private boolean mHardwareAccelerated = false;
+
+ /**
+ * Enable multi-process ContentView. This should be called by the application before
+ * constructing any ContentView instances. If enabled, ContentView will run renderers in
+ * separate processes up to the number of processes specified by maxRenderProcesses. If this is
+ * not called then the default is to run the renderer in the main application on a separate
+ * thread.
+ *
+ * @param context Context used to obtain the application context.
+ * @param maxRendererProcesses Limit on the number of renderers to use. Each tab runs in its own
+ * process until the maximum number of processes is reached. The special value of
+ * MAX_RENDERERS_SINGLE_PROCESS requests single-process mode where the renderer will run in the
+ * application process in a separate thread. If the special value MAX_RENDERERS_AUTOMATIC is
+ * used then the number of renderers will be determined based on the device memory class. The
+ * maximum number of allowed renderers is capped by MAX_RENDERERS_LIMIT.
+ * @return Whether the process actually needed to be initialized (false if already running).
+ */
+ public static boolean enableMultiProcess(Context context, int maxRendererProcesses) {
+ return AndroidBrowserProcess.initContentViewProcess(context, maxRendererProcesses);
+ }
+
+ /**
+ * Initialize the process as the platform browser. This must be called before accessing
+ * ContentView in order to treat this as a Chromium browser process.
+ *
+ * @param context Context used to obtain the application context.
+ * @param maxRendererProcesses Same as ContentView.enableMultiProcess()
+ * @return Whether the process actually needed to be initialized (false if already running).
+ */
+ public static boolean initChromiumBrowserProcess(Context context, int maxRendererProcesses) {
+ return AndroidBrowserProcess.initChromiumBrowserProcess(context, maxRendererProcesses);
+ }
+
+ /**
+ * Constructs a new ContentViewCore. Embedders must call initialize() after constructing
+ * a ContentViewCore and before using it.
+ *
+ * @param context The context used to create this.
+ * @param personality The type of ContentViewCore being created.
+ */
+ public ContentViewCore(Context context, int personality) {
+ mContext = context;
+
+ WeakContext.initializeWeakContext(context);
+ // By default, ContentView will initialize single process mode. The call to
+ // initContentViewProcess below is ignored if either the ContentView host called
+ // enableMultiProcess() or the platform browser called initChromiumBrowserProcess().
+ AndroidBrowserProcess.initContentViewProcess(
+ context, AndroidBrowserProcess.MAX_RENDERERS_SINGLE_PROCESS);
+
+ mPersonality = personality;
+ HeapStatsLogger.init(mContext.getApplicationContext());
+ }
+
+ /**
+ * @return The context used for creating this ContentViewCore.
+ */
+ public Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * @return The ViewGroup that all view actions of this ContentViewCore should interact with.
+ */
+ protected ViewGroup getContainerView() {
+ return mContainerView;
+ }
+
+ private ImeAdapter createImeAdapter(Context context) {
+ return new ImeAdapter(context, getSelectionHandleController(),
+ getInsertionHandleController(),
+ new ImeAdapter.ViewEmbedder() {
+ @Override
+ public void onImeEvent(boolean isFinish) {
+ getContentViewClient().onImeEvent();
+ if (!isFinish) {
+ undoScrollFocusedEditableNodeIntoViewIfNeeded(false);
+ }
+ }
+
+ @Override
+ public void onSetFieldValue() {
+ scrollFocusedEditableNodeIntoView();
+ }
+
+ @Override
+ public void onDismissInput() {
+ }
+
+ @Override
+ public View getAttachedView() {
+ return mContainerView;
+ }
+
+ @Override
+ public ResultReceiver getNewShowKeyboardReceiver() {
+ return new ResultReceiver(new Handler()) {
+ @Override
+ public void onReceiveResult(int resultCode, Bundle resultData) {
+ if (resultCode == InputMethodManager.RESULT_SHOWN) {
+ // If OSK is newly shown, delay the form focus until
+ // the onSizeChanged (in order to adjust relative to the
+ // new size).
+ mFocusOnNextSizeChanged = true;
+ } else if (resultCode ==
+ InputMethodManager.RESULT_UNCHANGED_SHOWN) {
+ // If the OSK was already there, focus the form immediately.
+ scrollFocusedEditableNodeIntoView();
+ } else {
+ undoScrollFocusedEditableNodeIntoViewIfNeeded(false);
+ }
+ }
+ };
+ }
+ }
+ );
+ }
+
+ /**
+ * Returns true if the given Activity has hardware acceleration enabled
+ * in its manifest, or in its foreground window.
+ *
+ * TODO(husky): Remove when initialize() is refactored (see TODO there)
+ * TODO(dtrainor) This is still used by other classes. Make sure to pull some version of this
+ * out before removing it.
+ */
+ public static boolean hasHardwareAcceleration(Activity activity) {
+ // Has HW acceleration been enabled manually in the current window?
+ Window window = activity.getWindow();
+ if (window != null) {
+ if ((window.getAttributes().flags
+ & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0) {
+ return true;
+ }
+ }
+
+ // Has HW acceleration been enabled in the manifest?
+ try {
+ ActivityInfo info = activity.getPackageManager().getActivityInfo(
+ activity.getComponentName(), 0);
+ if ((info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
+ return true;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e("Chrome", "getActivityInfo(self) should not fail");
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns true if the given Context is a HW-accelerated Activity.
+ *
+ * TODO(husky): Remove when initialize() is refactored (see TODO there)
+ */
+ private static boolean hasHardwareAcceleration(Context context) {
+ if (context instanceof Activity) {
+ return hasHardwareAcceleration((Activity) context);
+ }
+ return false;
+ }
+
+ /**
+ *
+ * @param containerView The view that will act as a container for all views created by this.
+ * @param internalDispatcher Handles dispatching all hidden or super methods to the
+ * containerView.
+ * @param nativeWebContents A pointer to the native web contents.
+ * @param nativeWindow An instance of the NativeWindow.
+ * @param isAccessFromFileURLsGrantedByDefault Default WebSettings configuration.
+ */
+ // Perform important post-construction set up of the ContentViewCore.
+ // We do not require the containing view in the constructor to allow embedders to create a
+ // ContentViewCore without having fully created its containing view. The containing view
+ // is a vital component of the ContentViewCore, so embedders must exercise caution in what
+ // they do with the ContentViewCore before calling initialize().
+ // We supply the nativeWebContents pointer here rather than in the constructor to allow us
+ // to set the private browsing mode at a later point for the WebView implementation.
+ // Note that the caller remains the owner of the nativeWebContents and is responsible for
+ // deleting it after destroying the ContentViewCore.
+ public void initialize(ViewGroup containerView, InternalAccessDelegate internalDispatcher,
+ int nativeWebContents, NativeWindow nativeWindow,
+ boolean isAccessFromFileURLsGrantedByDefault) {
+ // Check whether to use hardware acceleration. This is a bit hacky, and
+ // only works if the Context is actually an Activity (as it is in the
+ // Chrome application).
+ //
+ // What we're doing here is checking whether the app has *requested*
+ // hardware acceleration by setting the appropriate flags. This does not
+ // necessarily mean we're going to *get* hardware acceleration -- that's
+ // up to the Android framework.
+ //
+ // TODO(husky): Once the native code has been updated so that the
+ // HW acceleration flag can be set dynamically (Grace is doing this),
+ // move this check into onAttachedToWindow(), where we can test for
+ // HW support directly.
+ mHardwareAccelerated = hasHardwareAcceleration(mContext);
+ mContainerView = containerView;
+ mNativeContentViewCore = nativeInit(mHardwareAccelerated, nativeWebContents,
+ nativeWindow.getNativePointer());
+ mCleanupReference = new CleanupReference(
+ this, new DestroyRunnable(mNativeContentViewCore));
+ mContentSettings = new ContentSettings(
+ this, mNativeContentViewCore, isAccessFromFileURLsGrantedByDefault);
+ initializeContainerView(internalDispatcher);
+ if (mPersonality == PERSONALITY_VIEW) {
+ setAllUserAgentOverridesInHistory();
+ }
+
+ mAccessibilityInjector = AccessibilityInjector.newInstance(this);
+ mAccessibilityInjector.addOrRemoveAccessibilityApisIfNecessary();
+
+ String contentDescription = "Web View";
+ if (AppResource.STRING_CONTENT_VIEW_CONTENT_DESCRIPTION == 0) {
+ Log.w(TAG, "Setting contentDescription to 'Web View' as no value was specified.");
+ } else {
+ contentDescription = mContext.getResources().getString(
+ AppResource.STRING_CONTENT_VIEW_CONTENT_DESCRIPTION);
+ }
+ mContainerView.setContentDescription(contentDescription);
+ mWebContentsObserver = new WebContentsObserverAndroid(this) {
+ @Override
+ public void didStartLoading(String url) {
+ hidePopupDialog();
+ }
+ };
+ }
+
+ /**
+ * Initializes the View that will contain all Views created by the ContentViewCore.
+ *
+ * @param internalDispatcher Handles dispatching all hidden or super methods to the
+ * containerView.
+ */
+ private void initializeContainerView(InternalAccessDelegate internalDispatcher) {
+ TraceEvent.begin();
+ mContainerViewInternals = internalDispatcher;
+
+ mContainerView.setWillNotDraw(false);
+ mContainerView.setFocusable(true);
+ mContainerView.setFocusableInTouchMode(true);
+ mContainerView.setClickable(true);
+
+ if (mPersonality == PERSONALITY_CHROME) {
+ // Doing this in PERSONALITY_VIEW mode causes rendering problems in our
+ // current WebView test case (the HTMLViewer application).
+ // TODO(benm): Figure out why this is the case.
+ if (mContainerView.getScrollBarStyle() == View.SCROLLBARS_INSIDE_OVERLAY) {
+ mContainerView.setHorizontalScrollBarEnabled(false);
+ mContainerView.setVerticalScrollBarEnabled(false);
+ }
+ }
+
+ mZoomManager = new ZoomManager(mContext, this);
+ mZoomManager.updateMultiTouchSupport();
+ mContentViewGestureHandler = new ContentViewGestureHandler(mContext, this, mZoomManager);
+
+ mNativeScrollX = mNativeScrollY = 0;
+ mNativePageScaleFactor = 1.0f;
+ initPopupZoomer(mContext);
+ mImeAdapter = createImeAdapter(mContext);
+ mKeyboardConnected = mContainerView.getResources().getConfiguration().keyboard
+ != Configuration.KEYBOARD_NOKEYS;
+ mVSyncMonitor = new VSyncMonitor(mContext, mContainerView, mVSyncListener);
+ TraceEvent.end();
+ }
+
+ private void initPopupZoomer(Context context){
+ mPopupZoomer = new PopupZoomer(context);
+ mContainerView.addView(mPopupZoomer);
+ PopupZoomer.OnTapListener listener = new PopupZoomer.OnTapListener() {
+ @Override
+ public boolean onSingleTap(View v, MotionEvent e) {
+ mContainerView.requestFocus();
+ if (mNativeContentViewCore != 0) {
+ nativeSingleTap(mNativeContentViewCore, e.getEventTime(), (int) e.getX(),
+ (int) e.getY(), true);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onLongPress(View v, MotionEvent e) {
+ if (mNativeContentViewCore != 0) {
+ nativeLongPress(mNativeContentViewCore, e.getEventTime(), (int) e.getX(),
+ (int) e.getY(), true);
+ }
+ return true;
+ }
+ };
+ mPopupZoomer.setOnTapListener(listener);
+ }
+
+ /**
+ * @return Whether the configured personality of this ContentView is {@link #PERSONALITY_VIEW}.
+ */
+ boolean isPersonalityView() {
+ switch (mPersonality) {
+ case PERSONALITY_VIEW:
+ return true;
+ case PERSONALITY_CHROME:
+ return false;
+ default:
+ Log.e(TAG, "Unknown ContentView personality: " + mPersonality);
+ return false;
+ }
+ }
+
+ /**
+ * Destroy the internal state of the ContentView. This method may only be
+ * called after the ContentView has been removed from the view system. No
+ * other methods may be called on this ContentView after this method has
+ * been called.
+ */
+ public void destroy() {
+ hidePopupDialog();
+ mCleanupReference.cleanupNow();
+ mNativeContentViewCore = 0;
+ // Do not propagate the destroy() to settings, as the client may still hold a reference to
+ // that and could still be using it.
+ mContentSettings = null;
+ mVSyncMonitor.stop();
+ }
+
+ /**
+ * Returns true initially, false after destroy() has been called.
+ * It is illegal to call any other public method after destroy().
+ */
+ public boolean isAlive() {
+ return mNativeContentViewCore != 0;
+ }
+
+ /**
+ * This is only useful for passing over JNI to native code that requires ContentViewCore*.
+ * @return native ContentViewCore pointer.
+ */
+ public int getNativeContentViewCore() {
+ return mNativeContentViewCore;
+ }
+
+ /**
+ * For internal use. Throws IllegalStateException if mNativeContentView is 0.
+ * Use this to ensure we get a useful Java stack trace, rather than a native
+ * crash dump, from use-after-destroy bugs in Java code.
+ */
+ void checkIsAlive() throws IllegalStateException {
+ if (!isAlive()) {
+ throw new IllegalStateException("ContentView used after destroy() was called");
+ }
+ }
+
+ public void setContentViewClient(ContentViewClient client) {
+ if (client == null) {
+ throw new IllegalArgumentException("The client can't be null.");
+ }
+ mContentViewClient = client;
+ }
+
+ ContentViewClient getContentViewClient() {
+ if (mContentViewClient == null) {
+ // We use the Null Object pattern to avoid having to perform a null check in this class.
+ // We create it lazily because most of the time a client will be set almost immediately
+ // after ContentView is created.
+ mContentViewClient = new ContentViewClient();
+ // We don't set the native ContentViewClient pointer here on purpose. The native
+ // implementation doesn't mind a null delegate and using one is better than passing a
+ // Null Object, since we cut down on the number of JNI calls.
+ }
+ return mContentViewClient;
+ }
+
+ public int getBackgroundColor() {
+ if (mNativeContentViewCore != 0) {
+ return nativeGetBackgroundColor(mNativeContentViewCore);
+ }
+ return Color.WHITE;
+ }
+
+ public void setBackgroundColor(int color) {
+ if (mNativeContentViewCore != 0 && getBackgroundColor() != color) {
+ nativeSetBackgroundColor(mNativeContentViewCore, color);
+ }
+ }
+
+ /**
+ * Load url without fixing up the url string. Consumers of ContentView are responsible for
+ * ensuring the URL passed in is properly formatted (i.e. the scheme has been added if left
+ * off during user input).
+ *
+ * @param pararms Parameters for this load.
+ */
+ public void loadUrl(LoadUrlParams params) {
+ if (mNativeContentViewCore == 0) return;
+ if (isPersonalityView()) {
+ // For WebView, always use the user agent override, which is set
+ // every time the user agent in ContentSettings is modified.
+ params.setOverrideUserAgent(LoadUrlParams.UA_OVERRIDE_TRUE);
+ }
+
+ nativeLoadUrl(mNativeContentViewCore,
+ params.mUrl,
+ params.mLoadUrlType,
+ params.mTransitionType,
+ params.mUaOverrideOption,
+ params.getExtraHeadersString(),
+ params.mPostData,
+ params.mBaseUrlForDataUrl,
+ params.mVirtualUrlForDataUrl);
+ }
+
+ void setAllUserAgentOverridesInHistory() {
+ nativeSetAllUserAgentOverridesInHistory(mNativeContentViewCore,
+ mContentSettings.getUserAgentString());
+ }
+
+ /**
+ * Stops loading the current web contents.
+ */
+ public void stopLoading() {
+ if (mNativeContentViewCore != 0) nativeStopLoading(mNativeContentViewCore);
+ }
+
+ /**
+ * Get the URL of the current page.
+ *
+ * @return The URL of the current page.
+ */
+ public String getUrl() {
+ if (mNativeContentViewCore != 0) return nativeGetURL(mNativeContentViewCore);
+ return null;
+ }
+
+ /**
+ * Get the title of the current page.
+ *
+ * @return The title of the current page.
+ */
+ public String getTitle() {
+ if (mNativeContentViewCore != 0) return nativeGetTitle(mNativeContentViewCore);
+ return null;
+ }
+
+ public int getWidth() {
+ return mContainerView.getWidth();
+ }
+
+ public int getHeight() {
+ return mContainerView.getHeight();
+ }
+
+ /**
+ * @see android.webkit.WebView#getContentHeight()
+ */
+ public int getContentHeight() {
+ return (int) (mContentHeight / mNativePageScaleFactor);
+ }
+
+ /**
+ * @see android.webkit.WebView#getContentWidth()
+ */
+ public int getContentWidth() {
+ return (int) (mContentWidth / mNativePageScaleFactor);
+ }
+
+ public Bitmap getBitmap() {
+ return getBitmap(getWidth(), getHeight());
+ }
+
+ public Bitmap getBitmap(int width, int height) {
+ return getBitmap(width, height, Bitmap.Config.ARGB_8888);
+ }
+
+ public Bitmap getBitmap(int width, int height, Bitmap.Config config) {
+ // TODO(nileshagrawal): Implement this.
+ return null;
+ }
+
+ /**
+ * @return Whether the ContentView is covered by an overlay that is more than half
+ * of it's surface. This is used to determine if we need to do a slow bitmap capture or
+ * to show the ContentView without them.
+ */
+ public boolean hasLargeOverlay() {
+ // TODO(nileshagrawal): Implement this.
+ return false;
+ }
+
+ /**
+ * @return Whether the current WebContents has a previous navigation entry.
+ */
+ public boolean canGoBack() {
+ return mNativeContentViewCore != 0 && nativeCanGoBack(mNativeContentViewCore);
+ }
+
+ /**
+ * @return Whether the current WebContents has a navigation entry after the current one.
+ */
+ public boolean canGoForward() {
+ return mNativeContentViewCore != 0 && nativeCanGoForward(mNativeContentViewCore);
+ }
+
+ /**
+ * @param offset The offset into the navigation history.
+ * @return Whether we can move in history by given offset
+ */
+ public boolean canGoToOffset(int offset) {
+ return mNativeContentViewCore != 0 && nativeCanGoToOffset(mNativeContentViewCore, offset);
+ }
+
+ /**
+ * Navigates to the specified offset from the "current entry". Does nothing if the offset is out
+ * of bounds.
+ * @param offset The offset into the navigation history.
+ */
+ public void goToOffset(int offset) {
+ if (mNativeContentViewCore != 0) nativeGoToOffset(mNativeContentViewCore, offset);
+ }
+
+ /**
+ * Goes to the navigation entry before the current one.
+ */
+ public void goBack() {
+ if (mNativeContentViewCore != 0) nativeGoBack(mNativeContentViewCore);
+ }
+
+ /**
+ * Goes to the navigation entry following the current one.
+ */
+ public void goForward() {
+ if (mNativeContentViewCore != 0) nativeGoForward(mNativeContentViewCore);
+ }
+
+ /**
+ * Reload the current page.
+ */
+ public void reload() {
+ mAccessibilityInjector.addOrRemoveAccessibilityApisIfNecessary();
+ if (mNativeContentViewCore != 0) nativeReload(mNativeContentViewCore);
+ }
+
+ /**
+ * Clears the ContentViewCore's page history in both the backwards and
+ * forwards directions.
+ */
+ public void clearHistory() {
+ if (mNativeContentViewCore != 0) nativeClearHistory(mNativeContentViewCore);
+ }
+
+ String getSelectedText() {
+ return mHasSelection ? mLastSelectedText : "";
+ }
+
+ // End FrameLayout overrides.
+
+ /**
+ * @see {@link android.webkit.WebView#flingScroll(int, int)}
+ */
+ public void flingScroll(int vx, int vy) {
+ // Notes:
+ // (1) Use large negative values for the x/y parameters so we don't accidentally scroll a
+ // nested frame.
+ // (2) vx and vy are inverted to match WebView behavior.
+ mContentViewGestureHandler.fling(
+ System.currentTimeMillis(), -Integer.MAX_VALUE, -Integer.MIN_VALUE, -vx, -vy);
+ }
+
+ /**
+ * @see View#onTouchEvent(MotionEvent)
+ */
+ public boolean onTouchEvent(MotionEvent event) {
+ undoScrollFocusedEditableNodeIntoViewIfNeeded(false);
+ return mContentViewGestureHandler.onTouchEvent(event);
+ }
+
+ /**
+ * @return ContentViewGestureHandler for all MotionEvent and gesture related calls.
+ */
+ ContentViewGestureHandler getContentViewGestureHandler() {
+ return mContentViewGestureHandler;
+ }
+
+ @Override
+ public boolean sendTouchEvent(long timeMs, int action, TouchPoint[] pts) {
+ if (mNativeContentViewCore != 0) {
+ return nativeSendTouchEvent(mNativeContentViewCore, timeMs, action, pts);
+ }
+ return false;
+ }
+
+ @SuppressWarnings("unused")
+ @CalledByNative
+ private void didSetNeedTouchEvents(boolean needTouchEvents) {
+ mContentViewGestureHandler.didSetNeedTouchEvents(needTouchEvents);
+ }
+
+ @SuppressWarnings("unused")
+ @CalledByNative
+ private void confirmTouchEvent(boolean handled) {
+ mContentViewGestureHandler.confirmTouchEvent(handled);
+ }
+
+ @Override
+ public boolean sendGesture(int type, long timeMs, int x, int y, Bundle b) {
+ if (mNativeContentViewCore == 0) return false;
+
+ switch (type) {
+ case ContentViewGestureHandler.GESTURE_SHOW_PRESSED_STATE:
+ nativeShowPressState(mNativeContentViewCore, timeMs, x, y);
+ return true;
+ case ContentViewGestureHandler.GESTURE_DOUBLE_TAP:
+ nativeDoubleTap(mNativeContentViewCore, timeMs, x, y);
+ return true;
+ case ContentViewGestureHandler.GESTURE_SINGLE_TAP_UP:
+ nativeSingleTap(mNativeContentViewCore, timeMs, x, y, false);
+ return true;
+ case ContentViewGestureHandler.GESTURE_SINGLE_TAP_CONFIRMED:
+ handleTapOrPress(timeMs, x, y, false,
+ b.getBoolean(ContentViewGestureHandler.SHOW_PRESS, false));
+ return true;
+ case ContentViewGestureHandler.GESTURE_LONG_PRESS:
+ handleTapOrPress(timeMs, x, y, true, false);
+ return true;
+ case ContentViewGestureHandler.GESTURE_SCROLL_START:
+ nativeScrollBegin(mNativeContentViewCore, timeMs, x, y);
+ return true;
+ case ContentViewGestureHandler.GESTURE_SCROLL_BY: {
+ int dx = b.getInt(ContentViewGestureHandler.DISTANCE_X);
+ int dy = b.getInt(ContentViewGestureHandler.DISTANCE_Y);
+ nativeScrollBy(mNativeContentViewCore, timeMs, x, y, dx, dy);
+ return true;
+ }
+ case ContentViewGestureHandler.GESTURE_SCROLL_END:
+ nativeScrollEnd(mNativeContentViewCore, timeMs);
+ return true;
+ case ContentViewGestureHandler.GESTURE_FLING_START:
+ nativeFlingStart(mNativeContentViewCore, timeMs, x, y,
+ clampFlingVelocityX(b.getInt(ContentViewGestureHandler.VELOCITY_X, 0)),
+ clampFlingVelocityY(b.getInt(ContentViewGestureHandler.VELOCITY_Y, 0)));
+ return true;
+ case ContentViewGestureHandler.GESTURE_FLING_CANCEL:
+ nativeFlingCancel(mNativeContentViewCore, timeMs);
+ return true;
+ case ContentViewGestureHandler.GESTURE_PINCH_BEGIN:
+ nativePinchBegin(mNativeContentViewCore, timeMs, x, y);
+ return true;
+ case ContentViewGestureHandler.GESTURE_PINCH_BY:
+ nativePinchBy(mNativeContentViewCore, timeMs, x, y,
+ b.getFloat(ContentViewGestureHandler.DELTA, 0));
+ return true;
+ case ContentViewGestureHandler.GESTURE_PINCH_END:
+ nativePinchEnd(mNativeContentViewCore, timeMs);
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Injects the passed JavaScript code in the current page and evaluates it.
+ * Once evaluated, an asynchronous call to
+ * ContentViewClient.onJavaScriptEvaluationResult is made. Used in automation
+ * tests.
+ *
+ * @return an id that is passed along in the asynchronous onJavaScriptEvaluationResult callback
+ * @throws IllegalStateException If the ContentView has been destroyed.
+ * @hide
+ */
+ public int evaluateJavaScript(String script) throws IllegalStateException {
+ checkIsAlive();
+ return nativeEvaluateJavaScript(script);
+ }
+
+ /**
+ * This method should be called when the containing activity is paused.
+ */
+ public void onActivityPause() {
+ TraceEvent.begin();
+ hidePopupDialog();
+ nativeOnHide(mNativeContentViewCore);
+ setAccessibilityState(false);
+ TraceEvent.end();
+ mVSyncMonitor.stop();
+ }
+
+ /**
+ * This method should be called when the containing activity is resumed.
+ */
+ public void onActivityResume() {
+ nativeOnShow(mNativeContentViewCore);
+ setAccessibilityState(true);
+ }
+
+ /**
+ * To be called when the ContentView is shown.
+ */
+ public void onShow() {
+ nativeOnShow(mNativeContentViewCore);
+ setAccessibilityState(true);
+ }
+
+ /**
+ * To be called when the ContentView is hidden.
+ */
+ public void onHide() {
+ hidePopupDialog();
+ setAccessibilityState(false);
+ nativeOnHide(mNativeContentViewCore);
+ mVSyncMonitor.stop();
+ }
+
+ /**
+ * Return the ContentSettings object used to control the settings for this
+ * ContentViewCore.
+ *
+ * Note that when ContentView is used in the PERSONALITY_CHROME role,
+ * ContentSettings can only be used for retrieving settings values. For
+ * modifications, ChromeNativePreferences is to be used.
+ * @return A ContentSettings object that can be used to control this
+ * ContentViewCore's settings.
+ */
+ public ContentSettings getContentSettings() {
+ return mContentSettings;
+ }
+
+ @Override
+ public boolean didUIStealScroll(float x, float y) {
+ return getContentViewClient().shouldOverrideScroll(
+ x, y, computeHorizontalScrollOffset(), computeVerticalScrollOffset());
+ }
+
+ private void hidePopupDialog() {
+ SelectPopupDialog.hide(this);
+ }
+
+ void hideSelectActionBar() {
+ if (mActionMode != null) {
+ mActionMode.finish();
+ }
+ }
+
+ /**
+ * @see View#onAttachedToWindow()
+ */
+ @SuppressWarnings("javadoc")
+ protected void onAttachedToWindow() {
+ setAccessibilityState(true);
+ }
+
+ /**
+ * @see View#onDetachedFromWindow()
+ */
+ @SuppressWarnings("javadoc")
+ protected void onDetachedFromWindow() {
+ setAccessibilityState(false);
+ }
+
+ @CalledByNative
+ private void onWebPreferencesUpdated() {
+ // TODO(nileshagrawal): Implement this.
+ }
+
+ /**
+ * @see View#onCreateInputConnection(EditorInfo)
+ */
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ if (!mImeAdapter.hasTextInputType()) {
+ // Although onCheckIsTextEditor will return false in this case, the EditorInfo
+ // is still used by the InputMethodService. Need to make sure the IME doesn't
+ // enter fullscreen mode.
+ outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN;
+ }
+ mInputConnection = ImeAdapter.AdapterInputConnection.getInstance(
+ mContainerView, mImeAdapter, outAttrs);
+ return mInputConnection;
+ }
+
+ /**
+ * @see View#onCheckIsTextEditor()
+ */
+ public boolean onCheckIsTextEditor() {
+ return mImeAdapter.hasTextInputType();
+ }
+
+ /**
+ * @see View#onConfigurationChanged(Configuration)
+ */
+ @SuppressWarnings("javadoc")
+ public void onConfigurationChanged(Configuration newConfig) {
+ TraceEvent.begin();
+
+ mKeyboardConnected = newConfig.keyboard != Configuration.KEYBOARD_NOKEYS;
+
+ if (mKeyboardConnected) {
+ mImeAdapter.attach(nativeGetNativeImeAdapter(mNativeContentViewCore),
+ ImeAdapter.sTextInputTypeNone);
+ InputMethodManager manager = (InputMethodManager)
+ getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ manager.restartInput(mContainerView);
+ }
+ mContainerViewInternals.super_onConfigurationChanged(newConfig);
+ mNeedUpdateOrientationChanged = true;
+ TraceEvent.end();
+ }
+
+ /**
+ * @see View#onSizeChanged(int, int, int, int)
+ */
+ @SuppressWarnings("javadoc")
+ public void onSizeChanged(int w, int h, int ow, int oh) {
+ mPopupZoomer.hide(false);
+ // Update the content size to make sure it is at least the View size
+ if (mContentWidth < w) mContentWidth = w;
+ if (mContentHeight < h) mContentHeight = h;
+ }
+
+ public void updateAfterSizeChanged() {
+ // Execute a delayed form focus operation because the OSK was brought
+ // up earlier.
+ if (mFocusOnNextSizeChanged) {
+ scrollFocusedEditableNodeIntoView();
+ mFocusOnNextSizeChanged = false;
+ } else if (mUnfocusOnNextSizeChanged) {
+ undoScrollFocusedEditableNodeIntoViewIfNeeded(true);
+ mUnfocusOnNextSizeChanged = false;
+ }
+ }
+
+ private void scrollFocusedEditableNodeIntoView() {
+ if (mNativeContentViewCore != 0) {
+ Runnable scrollTask = new Runnable() {
+ @Override
+ public void run() {
+ if (mNativeContentViewCore != 0) {
+ nativeScrollFocusedEditableNodeIntoView(mNativeContentViewCore);
+ }
+ }
+ };
+
+ scrollTask.run();
+
+ // The native side keeps track of whether the zoom and scroll actually occurred. It is
+ // more efficient to do it this way and sometimes fire an unnecessary message rather
+ // than synchronize with the renderer and always have an additional message.
+ mScrolledAndZoomedFocusedEditableNode = true;
+ }
+ }
+
+ private void undoScrollFocusedEditableNodeIntoViewIfNeeded(boolean backButtonPressed) {
+ // The only call to this function that matters is the first call after the
+ // scrollFocusedEditableNodeIntoView function call.
+ // If the first call to this function is a result of a back button press we want to undo the
+ // preceding scroll. If the call is a result of some other action we don't want to perform
+ // an undo.
+ // All subsequent calls are ignored since only the scroll function sets
+ // mScrolledAndZoomedFocusedEditableNode to true.
+ if (mScrolledAndZoomedFocusedEditableNode && backButtonPressed &&
+ mNativeContentViewCore != 0) {
+ Runnable scrollTask = new Runnable() {
+ @Override
+ public void run() {
+ if (mNativeContentViewCore != 0) {
+ nativeUndoScrollFocusedEditableNodeIntoView(mNativeContentViewCore);
+ }
+ }
+ };
+
+ scrollTask.run();
+ }
+ mScrolledAndZoomedFocusedEditableNode = false;
+ }
+
+ /**
+ * @see View#onFocusedChanged(boolean, int, Rect)
+ */
+ @SuppressWarnings("javadoc")
+ public void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
+ if (mNativeContentViewCore != 0) nativeSetFocus(mNativeContentViewCore, gainFocus);
+ }
+
+ /**
+ * @see View#onKeyUp(int, KeyEvent)
+ */
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (mPopupZoomer.isShowing() && keyCode == KeyEvent.KEYCODE_BACK) {
+ mPopupZoomer.hide(true);
+ return true;
+ }
+ return mContainerViewInternals.super_onKeyUp(keyCode, event);
+ }
+
+ /**
+ * @see View#dispatchKeyEventPreIme(KeyEvent)
+ */
+ public boolean dispatchKeyEventPreIme(KeyEvent event) {
+ try {
+ TraceEvent.begin();
+ if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && mImeAdapter.isActive()) {
+ mUnfocusOnNextSizeChanged = true;
+ } else {
+ undoScrollFocusedEditableNodeIntoViewIfNeeded(false);
+ }
+ mImeAdapter.dispatchKeyEventPreIme(event);
+ return mContainerViewInternals.super_dispatchKeyEventPreIme(event);
+ } finally {
+ TraceEvent.end();
+ }
+ }
+
+ /**
+ * @see View#dispatchKeyEvent(KeyEvent)
+ */
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (mImeAdapter != null &&
+ !mImeAdapter.isNativeImeAdapterAttached() && mNativeContentViewCore != 0) {
+ mImeAdapter.attach(nativeGetNativeImeAdapter(mNativeContentViewCore));
+ }
+ // The key handling logic is kind of confusing here.
+ // The purpose of shouldOverrideKeyEvent() is to filter out some keys that is critical
+ // to browser function but useless in renderer process (for example, the back button),
+ // so the browser can still respond to these keys in a timely manner when the renderer
+ // process is busy/blocked/busted. mImeAdapter.dispatchKeyEvent() forwards the key event
+ // to the renderer process. If mImeAdapter is bypassed or is not interested to the event,
+ // fall back to the default dispatcher to propagate the event to sub-views.
+ return (!getContentViewClient().shouldOverrideKeyEvent(event)
+ && mImeAdapter.dispatchKeyEvent(event))
+ || mContainerViewInternals.super_dispatchKeyEvent(event);
+ }
+
+ /**
+ * @see View#onHoverEvent(MotionEvent)
+ * Mouse move events are sent on hover enter, hover move and hover exit.
+ * They are sent on hover exit because sometimes it acts as both a hover
+ * move and hover exit.
+ */
+ public boolean onHoverEvent(MotionEvent event) {
+ TraceEvent.begin("onHoverEvent");
+ mContainerView.removeCallbacks(mFakeMouseMoveRunnable);
+ if (mNativeContentViewCore != 0) {
+ nativeSendMouseMoveEvent(mNativeContentViewCore, event.getEventTime(),
+ (int) event.getX(), (int) event.getY());
+ }
+ TraceEvent.end("onHoverEvent");
+ return true;
+ }
+
+ /**
+ * @see View#onGenericMotionEvent(MotionEvent)
+ */
+ public boolean onGenericMotionEvent(MotionEvent event) {
+ if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_SCROLL:
+ nativeSendMouseWheelEvent(mNativeContentViewCore, event.getEventTime(),
+ (int) event.getX(), (int) event.getY(),
+ event.getAxisValue(MotionEvent.AXIS_VSCROLL));
+
+ mContainerView.removeCallbacks(mFakeMouseMoveRunnable);
+ // Send a delayed onMouseMove event so that we end
+ // up hovering over the right position after the scroll.
+ final MotionEvent eventFakeMouseMove = MotionEvent.obtain(event);
+ mFakeMouseMoveRunnable = new Runnable() {
+ @Override
+ public void run() {
+ onHoverEvent(eventFakeMouseMove);
+ }
+ };
+ mContainerView.postDelayed(mFakeMouseMoveRunnable, 250);
+ return true;
+ }
+ }
+ return mContainerViewInternals.super_onGenericMotionEvent(event);
+ }
+
+ /**
+ * @see View#scrollBy(int, int)
+ * Currently the ContentView scrolling happens in the native side. In
+ * the Java view system, it is always pinned at (0, 0). scrollBy() and scrollTo()
+ * are overridden, so that View's mScrollX and mScrollY will be unchanged at
+ * (0, 0). This is critical for drawing ContentView correctly.
+ */
+ public void scrollBy(int x, int y) {
+ if (mNativeContentViewCore != 0) {
+ nativeScrollBy(mNativeContentViewCore, System.currentTimeMillis(), 0, 0, x, y);
+ }
+ }
+
+ /**
+ * @see View#scrollTo(int, int)
+ */
+ public void scrollTo(int x, int y) {
+ if (mNativeContentViewCore == 0) return;
+ int dx = x - mNativeScrollX, dy = y - mNativeScrollY;
+ if (dx != 0 || dy != 0) {
+ long time = System.currentTimeMillis();
+ nativeScrollBegin(mNativeContentViewCore, time, mNativeScrollX, mNativeScrollY);
+ nativeScrollBy(mNativeContentViewCore, time, mNativeScrollX, mNativeScrollY,
+ dx, dy);
+ nativeScrollEnd(mNativeContentViewCore, time);
+ }
+ }
+
+ // NOTE: this can go away once ContentView.getScrollX() reports correct values.
+ // see: b/6029133
+ public int getNativeScrollXForTest() {
+ return mNativeScrollX;
+ }
+
+ // NOTE: this can go away once ContentView.getScrollY() reports correct values.
+ // see: b/6029133
+ public int getNativeScrollYForTest() {
+ return mNativeScrollY;
+ }
+
+ /**
+ * @see View#computeHorizontalScrollExtent()
+ */
+ @SuppressWarnings("javadoc")
+ protected int computeHorizontalScrollExtent() {
+ // TODO (dtrainor): Need to expose scroll events properly to public. Either make getScroll*
+ // work or expose computeHorizontalScrollOffset()/computeVerticalScrollOffset as public.
+ return getWidth();
+ }
+
+ /**
+ * @see View#computeHorizontalScrollOffset()
+ */
+ @SuppressWarnings("javadoc")
+ public int computeHorizontalScrollOffset() {
+ return mNativeScrollX;
+ }
+
+ /**
+ * @see View#computeHorizontalScrollRange()
+ */
+ @SuppressWarnings("javadoc")
+ public int computeHorizontalScrollRange() {
+ return mContentWidth;
+ }
+
+ /**
+ * @see View#computeVerticalScrollExtent()
+ */
+ @SuppressWarnings("javadoc")
+ public int computeVerticalScrollExtent() {
+ return getHeight();
+ }
+
+ /**
+ * @see View#computeVerticalScrollOffset()
+ */
+ @SuppressWarnings("javadoc")
+ public int computeVerticalScrollOffset() {
+ return mNativeScrollY;
+ }
+
+ /**
+ * @see View#computeVerticalScrollRange()
+ */
+ @SuppressWarnings("javadoc")
+ public int computeVerticalScrollRange() {
+ return mContentHeight;
+ }
+
+ // End FrameLayout overrides.
+
+ /**
+ * @see View#awakenScrollBars(int, boolean)
+ */
+ @SuppressWarnings("javadoc")
+ protected boolean awakenScrollBars(int startDelay, boolean invalidate) {
+ // For the default implementation of ContentView which draws the scrollBars on the native
+ // side, calling this function may get us into a bad state where we keep drawing the
+ // scrollBars, so disable it by always returning false.
+ if (mContainerView.getScrollBarStyle() == View.SCROLLBARS_INSIDE_OVERLAY) {
+ return false;
+ } else {
+ return mContainerViewInternals.super_awakenScrollBars(startDelay, invalidate);
+ }
+ }
+
+ private void handleTapOrPress(
+ long timeMs, int x, int y, boolean isLongPress, boolean showPress) {
+ //TODO(yusufo):Upstream the rest of the bits about handlerControllers.
+ if (!mContainerView.isFocused()) mContainerView.requestFocus();
+
+ if (!mPopupZoomer.isShowing()) mPopupZoomer.setLastTouch(x, y);
+
+ if (isLongPress) {
+ if (mNativeContentViewCore != 0) {
+ nativeLongPress(mNativeContentViewCore, timeMs, x, y, false);
+ }
+ } else {
+ if (!showPress && mNativeContentViewCore != 0) {
+ nativeShowPressState(mNativeContentViewCore, timeMs, x, y);
+ }
+ if (mNativeContentViewCore != 0) {
+ nativeSingleTap(mNativeContentViewCore, timeMs, x, y, false);
+ }
+ }
+ }
+
+ void updateMultiTouchZoomSupport() {
+ mZoomManager.updateMultiTouchSupport();
+ }
+
+ public boolean isMultiTouchZoomSupported() {
+ return mZoomManager.isMultiTouchZoomSupported();
+ }
+
+ void selectPopupMenuItems(int[] indices) {
+ if (mNativeContentViewCore != 0) {
+ nativeSelectPopupMenuItems(mNativeContentViewCore, indices);
+ }
+ }
+
+ /*
+ * To avoid checkerboard, we clamp the fling velocity based on the maximum number of tiles
+ * allowed to be uploaded per 100ms. Calculation is limited to one direction. We assume the
+ * tile size is 256x256. The precise distance / velocity should be calculated based on the
+ * logic in Scroller.java. As it is almost linear for the first 100ms, we use a simple math.
+ */
+ private int clampFlingVelocityX(int velocity) {
+ int cols = mMaxNumUploadTiles / (int) (Math.ceil((float) getHeight() / 256) + 1);
+ int maxVelocity = cols > 0 ? cols * 2560 : 1000;
+ if (Math.abs(velocity) > maxVelocity) {
+ return velocity > 0 ? maxVelocity : -maxVelocity;
+ } else {
+ return velocity;
+ }
+ }
+
+ private int clampFlingVelocityY(int velocity) {
+ int rows = mMaxNumUploadTiles / (int) (Math.ceil((float) getWidth() / 256) + 1);
+ int maxVelocity = rows > 0 ? rows * 2560 : 1000;
+ if (Math.abs(velocity) > maxVelocity) {
+ return velocity > 0 ? maxVelocity : -maxVelocity;
+ } else {
+ return velocity;
+ }
+ }
+
+ /**
+ * Register the listener to be used when content can not be handled by the
+ * rendering engine, and should be downloaded instead. This will replace the
+ * current listener.
+ * @param listener An implementation of DownloadListener.
+ */
+ // TODO(nileshagrawal): decide if setDownloadDelegate will be public API. If so,
+ // this method should be deprecated and the javadoc should make reference to the
+ // fact that a ContentViewDownloadDelegate will be used in preference to a
+ // DownloadListener.
+ public void setDownloadListener(DownloadListener listener) {
+ mDownloadListener = listener;
+ }
+
+ // Called by DownloadController.
+ DownloadListener downloadListener() {
+ return mDownloadListener;
+ }
+
+ /**
+ * Register the delegate to be used when content can not be handled by
+ * the rendering engine, and should be downloaded instead. This will replace
+ * the current delegate or existing DownloadListner.
+ * Embedders should prefer this over the legacy DownloadListener.
+ * @param listener An implementation of ContentViewDownloadDelegate.
+ */
+ public void setDownloadDelegate(ContentViewDownloadDelegate delegate) {
+ mDownloadDelegate = delegate;
+ }
+
+ // Called by DownloadController.
+ ContentViewDownloadDelegate getDownloadDelegate() {
+ return mDownloadDelegate;
+ }
+
+ private SelectionHandleController getSelectionHandleController() {
+ if (mSelectionHandleController == null) {
+ mSelectionHandleController = new SelectionHandleController(getContainerView());
+ // TODO(olilan): add specific method implementations.
+
+ mSelectionHandleController.hideAndDisallowAutomaticShowing();
+ }
+
+ return mSelectionHandleController;
+ }
+
+ private InsertionHandleController getInsertionHandleController() {
+ if (mInsertionHandleController == null) {
+ mInsertionHandleController = new InsertionHandleController(getContainerView());
+ // TODO(olilan): add specific method implementations.
+
+ mInsertionHandleController.hideAndDisallowAutomaticShowing();
+ }
+
+ return mInsertionHandleController;
+ }
+
+ private void showSelectActionBar() {
+ if (mActionMode != null) {
+ mActionMode.invalidate();
+ return;
+ }
+
+ // Start a new action mode with a SelectActionModeCallback.
+ SelectActionModeCallback.ActionHandler actionHandler =
+ new SelectActionModeCallback.ActionHandler() {
+ @Override
+ public boolean selectAll() {
+ return mImeAdapter.selectAll();
+ }
+
+ @Override
+ public boolean cut() {
+ return mImeAdapter.cut();
+ }
+
+ @Override
+ public boolean copy() {
+ return mImeAdapter.copy();
+ }
+
+ @Override
+ public boolean paste() {
+ return mImeAdapter.paste();
+ }
+
+ @Override
+ public boolean isSelectionEditable() {
+ return mSelectionEditable;
+ }
+
+ @Override
+ public String getSelectedText() {
+ return ContentViewCore.this.getSelectedText();
+ }
+
+ @Override
+ public void onDestroyActionMode() {
+ mActionMode = null;
+ mImeAdapter.unselect();
+ getContentViewClient().onContextualActionBarHidden();
+ }
+ };
+ mActionMode = mContainerView.startActionMode(
+ getContentViewClient().getSelectActionModeCallback(getContext(), actionHandler,
+ nativeIsIncognito(mNativeContentViewCore)));
+ if (mActionMode == null) {
+ // There is no ActionMode, so remove the selection.
+ mImeAdapter.unselect();
+ } else {
+ getContentViewClient().onContextualActionBarShown();
+ }
+ }
+
+ public boolean getUseDesktopUserAgent() {
+ if (mNativeContentViewCore != 0) {
+ return nativeGetUseDesktopUserAgent(mNativeContentViewCore);
+ }
+ return false;
+ }
+
+ /**
+ * Set whether or not we're using a desktop user agent for the currently loaded page.
+ * @param override If true, use a desktop user agent. Use a mobile one otherwise.
+ * @param reloadOnChange Reload the page if the UA has changed.
+ */
+ public void setUseDesktopUserAgent(boolean override, boolean reloadOnChange) {
+ if (mNativeContentViewCore != 0) {
+ nativeSetUseDesktopUserAgent(mNativeContentViewCore, override, reloadOnChange);
+ }
+ }
+
+ /**
+ * @return Whether the native ContentView has crashed.
+ */
+ public boolean isCrashed() {
+ if (mNativeContentViewCore == 0) return false;
+ return nativeCrashed(mNativeContentViewCore);
+ }
+
+ @SuppressWarnings("unused")
+ @CalledByNative
+ private void updateContentSize(int width, int height) {
+ if (mContentWidth != width || mContentHeight != height) {
+ mPopupZoomer.hide(true);
+ }
+ // Make sure the content size is at least the View size
+ mContentWidth = Math.max(width, getWidth());
+ mContentHeight = Math.max(height, getHeight());
+ }
+
+ @SuppressWarnings("unused")
+ @CalledByNative
+ private void updateScrollOffsetAndPageScaleFactor(int x, int y, float scale) {
+ if (mNativeScrollX == x && mNativeScrollY == y && mNativePageScaleFactor == scale) return;
+
+ mContainerViewInternals.onScrollChanged(x, y, mNativeScrollX, mNativeScrollY);
+
+ // This function should be called back from native as soon
+ // as the scroll is applied to the backbuffer. We should only
+ // update mNativeScrollX/Y here for consistency.
+ mNativeScrollX = x;
+ mNativeScrollY = y;
+ mNativePageScaleFactor = scale;
+
+ mPopupZoomer.hide(true);
+
+ mZoomManager.updateZoomControls();
+ }
+
+ @SuppressWarnings("unused")
+ @CalledByNative
+ private void updatePageScaleLimits(float minimumScale, float maximumScale) {
+ mNativeMinimumScale = minimumScale;
+ mNativeMaximumScale = maximumScale;
+ mZoomManager.updateZoomControls();
+ }
+
+ @SuppressWarnings("unused")
+ @CalledByNative
+ private void imeUpdateAdapter(int nativeImeAdapterAndroid, int textInputType,
+ String text, int selectionStart, int selectionEnd,
+ int compositionStart, int compositionEnd, boolean showImeIfNeeded) {
+ TraceEvent.begin();
+
+ // Non-breaking spaces can cause the IME to get confused. Replace with normal spaces.
+ text = text.replace('\u00A0', ' ');
+
+ mSelectionEditable = (textInputType != ImeAdapter.sTextInputTypeNone);
+
+ mImeAdapter.attachAndShowIfNeeded(nativeImeAdapterAndroid, textInputType,
+ text, showImeIfNeeded);
+
+ if (mInputConnection != null) {
+ // In WebKit if there's a composition then the selection will usually be the
+ // same as the composition, whereas Android IMEs expect the selection to be
+ // just a caret at the end of the composition.
+ if (selectionStart == compositionStart && selectionEnd == compositionEnd) {
+ selectionStart = selectionEnd;
+ }
+ mInputConnection.setEditableText(text, selectionStart, selectionEnd,
+ compositionStart, compositionEnd);
+ }
+ TraceEvent.end();
+ }
+
+ @SuppressWarnings("unused")
+ @CalledByNative
+ private void setTitle(String title) {
+ getContentViewClient().onUpdateTitle(title);
+ }
+
+ /**
+ * Called (from native) when the <select> popup needs to be shown.
+ * @param items Items to show.
+ * @param enabled POPUP_ITEM_TYPEs for items.
+ * @param multiple Whether the popup menu should support multi-select.
+ * @param selectedIndices Indices of selected items.
+ */
+ @SuppressWarnings("unused")
+ @CalledByNative
+ private void showSelectPopup(String[] items, int[] enabled, boolean multiple,
+ int[] selectedIndices) {
+ SelectPopupDialog.show(this, items, enabled, multiple, selectedIndices);
+ }
+
+ @SuppressWarnings("unused")
+ @CalledByNative
+ private void onEvaluateJavaScriptResult(int id, String jsonResult) {
+ getContentViewClient().onEvaluateJavaScriptResult(id, jsonResult);
+ }
+
+ /**
+ * @return Whether a reload happens when this ContentView is activated.
+ */
+ public boolean needsReload() {
+ return mNativeContentViewCore != 0 && nativeNeedsReload(mNativeContentViewCore);
+ }
+
+ /**
+ * @see View#hasFocus()
+ */
+ @CalledByNative
+ public boolean hasFocus() {
+ return mContainerView.hasFocus();
+ }
+
+ /**
+ * Checks whether the ContentViewCore can be zoomed in.
+ *
+ * @return True if the ContentViewCore can be zoomed in.
+ */
+ // This method uses the term 'zoom' for legacy reasons, but relates
+ // to what chrome calls the 'page scale factor'.
+ public boolean canZoomIn() {
+ return mNativeMaximumScale - mNativePageScaleFactor > ZOOM_CONTROLS_EPSILON;
+ }
+
+ /**
+ * Checks whether the ContentViewCore can be zoomed out.
+ *
+ * @return True if the ContentViewCore can be zoomed out.
+ */
+ // This method uses the term 'zoom' for legacy reasons, but relates
+ // to what chrome calls the 'page scale factor'.
+ public boolean canZoomOut() {
+ return mNativePageScaleFactor - mNativeMinimumScale > ZOOM_CONTROLS_EPSILON;
+ }
+
+ /**
+ * Zooms in the ContentViewCore by 25% (or less if that would result in
+ * zooming in more than possible).
+ *
+ * @return True if there was a zoom change, false otherwise.
+ */
+ // This method uses the term 'zoom' for legacy reasons, but relates
+ // to what chrome calls the 'page scale factor'.
+ public boolean zoomIn() {
+ if (!canZoomIn()) {
+ return false;
+ }
+ return zoomByDelta(1.25f);
+ }
+
+ /**
+ * Zooms out the ContentViewCore by 20% (or less if that would result in
+ * zooming out more than possible).
+ *
+ * @return True if there was a zoom change, false otherwise.
+ */
+ // This method uses the term 'zoom' for legacy reasons, but relates
+ // to what chrome calls the 'page scale factor'.
+ public boolean zoomOut() {
+ if (!canZoomOut()) {
+ return false;
+ }
+ return zoomByDelta(0.8f);
+ }
+
+ /**
+ * Resets the zoom factor of the ContentViewCore.
+ *
+ * @return True if there was a zoom change, false otherwise.
+ */
+ // This method uses the term 'zoom' for legacy reasons, but relates
+ // to what chrome calls the 'page scale factor'.
+ public boolean zoomReset() {
+ // The page scale factor is initialized to mNativeMinimumScale when
+ // the page finishes loading. Thus sets it back to mNativeMinimumScale.
+ if (mNativePageScaleFactor - mNativeMinimumScale < ZOOM_CONTROLS_EPSILON) {
+ return false;
+ }
+ return zoomByDelta(mNativeMinimumScale / mNativePageScaleFactor);
+ }
+
+ private boolean zoomByDelta(float delta) {
+ if (mNativeContentViewCore == 0) {
+ return false;
+ }
+
+ long timeMs = System.currentTimeMillis();
+ int x = getWidth() / 2;
+ int y = getHeight() / 2;
+
+ getContentViewGestureHandler().pinchBegin(timeMs, x, y);
+ getContentViewGestureHandler().pinchBy(timeMs, x, y, delta);
+ getContentViewGestureHandler().pinchEnd(timeMs);
+
+ return true;
+ }
+
+ /**
+ * Invokes the graphical zoom picker widget for this ContentView.
+ */
+ @Override
+ public void invokeZoomPicker() {
+ if (mContentSettings.supportZoom()) {
+ mZoomManager.invokeZoomPicker();
+ }
+ }
+
+ // Unlike legacy WebView getZoomControls which returns external zoom controls,
+ // this method returns built-in zoom controls. This method is used in tests.
+ public View getZoomControlsForTest() {
+ return mZoomManager.getZoomControlsViewForTest();
+ }
+
+ /**
+ * This method injects the supplied Java object into the ContentViewCore.
+ * The object is injected into the JavaScript context of the main frame,
+ * using the supplied name. This allows the Java object to be accessed from
+ * JavaScript. Note that that injected objects will not appear in
+ * JavaScript until the page is next (re)loaded. For example:
+ * <pre> view.addJavascriptInterface(new Object(), "injectedObject");
+ * view.loadData("<!DOCTYPE html><title></title>", "text/html", null);
+ * view.loadUrl("javascript:alert(injectedObject.toString())");</pre>
+ * <p><strong>IMPORTANT:</strong>
+ * <ul>
+ * <li> addJavascriptInterface() can be used to allow JavaScript to control
+ * the host application. This is a powerful feature, but also presents a
+ * security risk. Use of this method in a ContentViewCore containing
+ * untrusted content could allow an attacker to manipulate the host
+ * application in unintended ways, executing Java code with the permissions
+ * of the host application. Use extreme care when using this method in a
+ * ContentViewCore which could contain untrusted content. Particular care
+ * should be taken to avoid unintentional access to inherited methods, such
+ * as {@link Object#getClass()}. To prevent access to inherited methods,
+ * set {@code allowInheritedMethods} to {@code false}. In addition, ensure
+ * that the injected object's public methods return only objects designed
+ * to be used by untrusted code, and never return a raw Object instance.
+ * <li> JavaScript interacts with Java objects on a private, background
+ * thread of the ContentViewCore. Care is therefore required to maintain
+ * thread safety.</li>
+ * </ul></p>
+ *
+ * @param object The Java object to inject into the ContentViewCore's
+ * JavaScript context. Null values are ignored.
+ * @param name The name used to expose the instance in JavaScript.
+ * @param requireAnnotation Restrict exposed methods to ones with the
+ * {@link JavascriptInterface} annotation.
+ */
+ public void addJavascriptInterface(Object object, String name, boolean requireAnnotation) {
+ if (mNativeContentViewCore != 0 && object != null) {
+ nativeAddJavascriptInterface(mNativeContentViewCore, object, name, requireAnnotation);
+ }
+ }
+
+ /**
+ * Removes a previously added JavaScript interface with the given name.
+ *
+ * @param name The name of the interface to remove.
+ */
+ public void removeJavascriptInterface(String name) {
+ if (mNativeContentViewCore != 0) {
+ nativeRemoveJavascriptInterface(mNativeContentViewCore, name);
+ }
+ }
+
+ /**
+ * Return the current scale of the ContentView.
+ * @return The current scale.
+ */
+ public float getScale() {
+ return mNativePageScaleFactor;
+ }
+
+ /**
+ * If the view is ready to draw contents to the screen. In hardware mode,
+ * the initialization of the surface texture may not occur until after the
+ * view has been added to the layout. This method will return {@code true}
+ * once the texture is actually ready.
+ */
+ public boolean isReady() {
+ // TODO(nileshagrawal): Implement this.
+ return false;
+ }
+
+ /**
+ * @return Whether or not the texture view is available or not.
+ */
+ public boolean isAvailable() {
+ // TODO(nileshagrawal): Implement this.
+ return false;
+ }
+
+ @CalledByNative
+ private void startContentIntent(String contentUrl) {
+ getContentViewClient().onStartContentIntent(getContext(), contentUrl);
+ }
+
+ /**
+ * Determines whether or not this ContentViewCore can handle this accessibility action.
+ * @param action The action to perform.
+ * @return Whether or not this action is supported.
+ */
+ public boolean supportsAccessibilityAction(int action) {
+ return mAccessibilityInjector.supportsAccessibilityAction(action);
+ }
+
+ /**
+ * Attempts to perform an accessibility action on the web content. If the accessibility action
+ * cannot be processed, it returns {@code null}, allowing the caller to know to call the
+ * super {@link View#performAccessibilityAction(int, Bundle)} method and use that return value.
+ * Otherwise the return value from this method should be used.
+ * @param action The action to perform.
+ * @param arguments Optional action arguments.
+ * @return Whether the action was performed or {@code null} if the call should be delegated to
+ * the super {@link View} class.
+ */
+ public boolean performAccessibilityAction(int action, Bundle arguments) {
+ if (mAccessibilityInjector.supportsAccessibilityAction(action)) {
+ return mAccessibilityInjector.performAccessibilityAction(action, arguments);
+ }
+
+ return false;
+ }
+
+ /**
+ * @see View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)
+ */
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ mAccessibilityInjector.onInitializeAccessibilityNodeInfo(info);
+ }
+
+ /**
+ * @see View#onInitializeAccessibilityEvent(AccessibilityEvent)
+ */
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ event.setClassName(this.getClass().getName());
+
+ // Identify where the top-left of the screen currently points to.
+ event.setScrollX(mNativeScrollX);
+ event.setScrollY(mNativeScrollY);
+
+ // The maximum scroll values are determined by taking the content dimensions and
+ // subtracting off the actual dimensions of the ChromeView.
+ int maxScrollX = Math.max(0, mContentWidth - getWidth());
+ int maxScrollY = Math.max(0, mContentHeight - getHeight());
+ event.setScrollable(maxScrollX > 0 || maxScrollY > 0);
+
+ // Setting the maximum scroll values requires API level 15 or higher.
+ final int SDK_VERSION_REQUIRED_TO_SET_SCROLL = 15;
+ if (Build.VERSION.SDK_INT >= SDK_VERSION_REQUIRED_TO_SET_SCROLL) {
+ event.setMaxScrollX(maxScrollX);
+ event.setMaxScrollY(maxScrollY);
+ }
+ }
+
+ /**
+ * Returns whether or not accessibility injection is being used.
+ */
+ public boolean isInjectingAccessibilityScript() {
+ return mAccessibilityInjector.accessibilityIsAvailable();
+ }
+
+ /**
+ * Enable or disable accessibility features.
+ */
+ public void setAccessibilityState(boolean state) {
+ mAccessibilityInjector.setScriptEnabled(state);
+ }
+
+ /**
+ * Stop any TTS notifications that are currently going on.
+ */
+ public void stopCurrentAccessibilityNotifications() {
+ mAccessibilityInjector.onPageLostFocus();
+ }
+
+ /**
+ * @See android.webkit.WebView#pageDown(boolean)
+ */
+ public boolean pageDown(boolean bottom) {
+ if (computeVerticalScrollOffset() >= mContentHeight - getHeight()) {
+ // We seem to already be at the bottom of the page, so no scrolling will occur.
+ return false;
+ }
+
+ if (bottom) {
+ scrollTo(computeHorizontalScrollOffset(), mContentHeight - getHeight());
+ } else {
+ dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_PAGE_DOWN));
+ dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_PAGE_DOWN));
+ }
+ return true;
+ }
+
+ /**
+ * @See android.webkit.WebView#pageUp(boolean)
+ */
+ public boolean pageUp(boolean top) {
+ if (computeVerticalScrollOffset() == 0) {
+ // We seem to already be at the top of the page, so no scrolling will occur.
+ return false;
+ }
+
+ if (top) {
+ scrollTo(computeHorizontalScrollOffset(), 0);
+ } else {
+ dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_PAGE_UP));
+ dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_PAGE_UP));
+ }
+ return true;
+ }
+
+ /**
+ * Callback factory method for nativeGetNavigationHistory().
+ */
+ @CalledByNative
+ private void addToNavigationHistory(Object history, String url, String virtualUrl,
+ String originalUrl, String title, Bitmap favicon) {
+ NavigationEntry entry = new NavigationEntry(url, virtualUrl, originalUrl, title, favicon);
+ ((NavigationHistory) history).addEntry(entry);
+ }
+
+ /**
+ * Get a copy of the navigation history of the view.
+ */
+ public NavigationHistory getNavigationHistory() {
+ NavigationHistory history = new NavigationHistory();
+ int currentIndex = nativeGetNavigationHistory(mNativeContentViewCore, history);
+ history.setCurrentEntryIndex(currentIndex);
+ return history;
+ }
+
+ private native int nativeInit(boolean hardwareAccelerated, int webContentsPtr,
+ int windowAndroidPtr);
+
+ private static native void nativeDestroy(int nativeContentViewCore);
+
+ private native void nativeLoadUrl(
+ int nativeContentViewCoreImpl,
+ String url,
+ int loadUrlType,
+ int transitionType,
+ int uaOverrideOption,
+ String extraHeaders,
+ byte[] postData,
+ String baseUrlForDataUrl,
+ String virtualUrlForDataUrl);
+
+ private native void nativeSetAllUserAgentOverridesInHistory(int nativeContentViewCoreImpl,
+ String userAgentOverride);
+
+ private native String nativeGetURL(int nativeContentViewCoreImpl);
+
+ private native String nativeGetTitle(int nativeContentViewCoreImpl);
+
+ private native boolean nativeIsIncognito(int nativeContentViewCoreImpl);
+
+ // Returns true if the native side crashed so that java side can draw a sad tab.
+ private native boolean nativeCrashed(int nativeContentViewCoreImpl);
+
+ private native void nativeSetFocus(int nativeContentViewCoreImpl, boolean focused);
+
+ private native boolean nativeSendTouchEvent(
+ int nativeContentViewCoreImpl, long timeMs, int action, TouchPoint[] pts);
+
+ private native int nativeSendMouseMoveEvent(
+ int nativeContentViewCoreImpl, long timeMs, int x, int y);
+
+ private native int nativeSendMouseWheelEvent(
+ int nativeContentViewCoreImpl, long timeMs, int x, int y, float verticalAxis);
+
+ private native void nativeScrollBegin(int nativeContentViewCoreImpl, long timeMs, int x, int y);
+
+ private native void nativeScrollEnd(int nativeContentViewCoreImpl, long timeMs);
+
+ private native void nativeScrollBy(
+ int nativeContentViewCoreImpl, long timeMs, int x, int y, int deltaX, int deltaY);
+
+ private native void nativeFlingStart(
+ int nativeContentViewCoreImpl, long timeMs, int x, int y, int vx, int vy);
+
+ private native void nativeFlingCancel(int nativeContentViewCoreImpl, long timeMs);
+
+ private native void nativeSingleTap(
+ int nativeContentViewCoreImpl, long timeMs, int x, int y, boolean linkPreviewTap);
+
+ private native void nativeShowPressState(
+ int nativeContentViewCoreImpl, long timeMs, int x, int y);
+
+ private native void nativeDoubleTap(int nativeContentViewCoreImpl, long timeMs, int x, int y);
+
+ private native void nativeLongPress(int nativeContentViewCoreImpl, long timeMs, int x, int y,
+ boolean linkPreviewTap);
+
+ private native void nativePinchBegin(int nativeContentViewCoreImpl, long timeMs, int x, int y);
+
+ private native void nativePinchEnd(int nativeContentViewCoreImpl, long timeMs);
+
+ private native void nativePinchBy(int nativeContentViewCoreImpl, long timeMs,
+ int anchorX, int anchorY, float deltaScale);
+
+ private native void nativeSelectBetweenCoordinates(
+ int nativeContentViewCoreImpl, int x1, int y1, int x2, int y2);
+
+ private native boolean nativeCanGoBack(int nativeContentViewCoreImpl);
+
+ private native boolean nativeCanGoForward(int nativeContentViewCoreImpl);
+
+ private native boolean nativeCanGoToOffset(int nativeContentViewCoreImpl, int offset);
+
+ private native void nativeGoToOffset(int nativeContentViewCoreImpl, int offset);
+
+ private native void nativeGoBack(int nativeContentViewCoreImpl);
+
+ private native void nativeGoForward(int nativeContentViewCoreImpl);
+
+ private native void nativeStopLoading(int nativeContentViewCoreImpl);
+
+ private native void nativeReload(int nativeContentViewCoreImpl);
+
+ private native void nativeSelectPopupMenuItems(int nativeContentViewCoreImpl, int[] indices);
+
+ private native void nativeScrollFocusedEditableNodeIntoView(int nativeContentViewCoreImpl);
+ private native void nativeUndoScrollFocusedEditableNodeIntoView(int nativeContentViewCoreImpl);
+ private native boolean nativeNeedsReload(int nativeContentViewCoreImpl);
+
+ private native void nativeClearHistory(int nativeContentViewCoreImpl);
+
+ private native int nativeEvaluateJavaScript(String script);
+
+ private native int nativeGetNativeImeAdapter(int nativeContentViewCoreImpl);
+
+ private native int nativeGetCurrentRenderProcessId(int nativeContentViewCoreImpl);
+
+ private native int nativeGetBackgroundColor(int nativeContentViewCoreImpl);
+
+ private native void nativeSetBackgroundColor(int nativeContentViewCoreImpl, int color);
+
+ private native void nativeOnShow(int nativeContentViewCoreImpl);
+ private native void nativeOnHide(int nativeContentViewCoreImpl);
+
+ private native void nativeSetUseDesktopUserAgent(int nativeContentViewCoreImpl,
+ boolean enabled, boolean reloadOnChange);
+ private native boolean nativeGetUseDesktopUserAgent(int nativeContentViewCoreImpl);
+
+ private native void nativeAddJavascriptInterface(int nativeContentViewCoreImpl, Object object,
+ String name, boolean requireAnnotation);
+
+ private native void nativeRemoveJavascriptInterface(int nativeContentViewCoreImpl, String name);
+
+ private native int nativeGetNavigationHistory(int nativeContentViewCoreImpl, Object context);
+
+ private native void nativeUpdateVSyncParameters(int nativeContentViewCoreImpl,
+ long timebaseMicros, long intervalMicros);
+}
diff --git a/content/public/android/java/src/org/chromium/content/browser/CursorController.java b/content/public/android/java/src/org/chromium/content/browser/CursorController.java
new file mode 100644
index 0000000..6e572c8
--- /dev/null
+++ b/content/public/android/java/src/org/chromium/content/browser/CursorController.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.content.browser;
+
+import android.view.ViewTreeObserver;
+
+/**
+ * A CursorController instance can be used to control a cursor in the text.
+ */
+interface CursorController extends ViewTreeObserver.OnTouchModeChangeListener {
+
+ /**
+ * Hide the cursor controller from screen.
+ */
+ void hide();
+
+ /**
+ * @return true if the CursorController is currently visible
+ */
+ boolean isShowing();
+
+ /**
+ * Called when the handle is about to start updating its position.
+ * @param handle
+ */
+ void beforeStartUpdatingPosition(HandleView handle);
+
+ /**
+ * Update the controller's position.
+ */
+ void updatePosition(HandleView handle, int x, int y);
+
+ /**
+ * Called when the view is detached from window. Perform house keeping task, such as
+ * stopping Runnable thread that would otherwise keep a reference on the context, thus
+ * preventing the activity to be recycled.
+ */
+ void onDetached();
+}
diff --git a/content/public/android/java/src/org/chromium/content/browser/HandleView.java b/content/public/android/java/src/org/chromium/content/browser/HandleView.java
new file mode 100644
index 0000000..bf36b56
--- /dev/null
+++ b/content/public/android/java/src/org/chromium/content/browser/HandleView.java
@@ -0,0 +1,370 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.content.browser;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.SystemClock;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.WindowManager;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.PopupWindow;
+import android.widget.TextView;
+
+/**
+ * View that displays a selection or insertion handle for text editing.
+ */
+class HandleView extends View {
+ private Drawable mDrawable;
+ private final PopupWindow mContainer;
+ private int mPositionX;
+ private int mPositionY;
+ private final CursorController mController;
+ private boolean mIsDragging;
+ private float mTouchToWindowOffsetX;
+ private float mTouchToWindowOffsetY;
+ private float mHotspotX;
+ private float mHotspotY;
+ private int mLineOffsetY;
+ private int mHeight;
+ private int mLastParentX;
+ private int mLastParentY;
+ private float mDownPositionX, mDownPositionY;
+ private int mContainerPositionX, mContainerPositionY;
+ private long mTouchTimer;
+ private boolean mIsInsertionHandle = false;
+ private Runnable mLongPressCallback;
+
+ private View mParent;
+ private InsertionHandleController.PastePopupMenu mPastePopupWindow;
+
+ private final int mTextSelectHandleLeftRes;
+ private final int mTextSelectHandleRightRes;
+ private final int mTextSelectHandleRes;
+
+ private Drawable mSelectHandleLeft;
+ private Drawable mSelectHandleRight;
+ private Drawable mSelectHandleCenter;
+
+ private final int[] mTempCoords = new int[2];
+ private final Rect mTempRect = new Rect();
+
+ static final int LEFT = 0;
+ static final int CENTER = 1;
+ static final int RIGHT = 2;
+
+ // Number of dips to subtract from the handle's y position to give a suitable
+ // y coordinate for the corresponding text position. This is to compensate for the fact
+ // that the handle position is at the base of the line of text.
+ private static final float LINE_OFFSET_Y_DIP = 5.0f;
+
+ private static final int[] TEXT_VIEW_HANDLE_ATTRS = {
+ android.R.attr.textSelectHandleLeft,
+ android.R.attr.textSelectHandle,
+ android.R.attr.textSelectHandleRight,
+ };
+
+ HandleView(CursorController controller, int pos, View parent) {
+ super(parent.getContext());
+ Context context = parent.getContext();
+ mParent = parent;
+ mController = controller;
+ mContainer = new PopupWindow(context, null, android.R.attr.textSelectHandleWindowStyle);
+ mContainer.setSplitTouchEnabled(true);
+ mContainer.setClippingEnabled(false);
+
+ TypedArray a = context.obtainStyledAttributes(TEXT_VIEW_HANDLE_ATTRS);
+ mTextSelectHandleLeftRes = a.getResourceId(a.getIndex(LEFT), 0);
+ mTextSelectHandleRes = a.getResourceId(a.getIndex(CENTER), 0);
+ mTextSelectHandleRightRes = a.getResourceId(a.getIndex(RIGHT), 0);
+ a.recycle();
+
+ setOrientation(pos);
+
+ // Convert line offset dips to pixels.
+ mLineOffsetY = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ LINE_OFFSET_Y_DIP, context.getResources().getDisplayMetrics());
+ }
+
+ void setOrientation(int pos) {
+ int handleWidth;
+ switch (pos) {
+ case LEFT: {
+ if (mSelectHandleLeft == null) {
+ mSelectHandleLeft = getContext().getResources().getDrawable(
+ mTextSelectHandleLeftRes);
+ }
+ mDrawable = mSelectHandleLeft;
+ handleWidth = mDrawable.getIntrinsicWidth();
+ mHotspotX = (handleWidth * 3) / 4;
+ break;
+ }
+
+ case RIGHT: {
+ if (mSelectHandleRight == null) {
+ mSelectHandleRight = getContext().getResources().getDrawable(
+ mTextSelectHandleRightRes);
+ }
+ mDrawable = mSelectHandleRight;
+ handleWidth = mDrawable.getIntrinsicWidth();
+ mHotspotX = handleWidth / 4;
+ break;
+ }
+
+ case CENTER:
+ default: {
+ if (mSelectHandleCenter == null) {
+ mSelectHandleCenter = getContext().getResources().getDrawable(
+ mTextSelectHandleRes);
+ }
+ mDrawable = mSelectHandleCenter;
+ handleWidth = mDrawable.getIntrinsicWidth();
+ mHotspotX = handleWidth / 2;
+ mIsInsertionHandle = true;
+ break;
+ }
+ }
+
+ final int handleHeight = mDrawable.getIntrinsicHeight();
+ mHotspotY = 0;
+ mHeight = handleHeight;
+ invalidate();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ setMeasuredDimension(mDrawable.getIntrinsicWidth(),
+ mDrawable.getIntrinsicHeight());
+ }
+
+ void show() {
+ if (!isPositionVisible()) {
+ hide();
+ return;
+ }
+ mContainer.setContentView(this);
+ final int[] coords = mTempCoords;
+ mParent.getLocationInWindow(coords);
+ mContainerPositionX = coords[0] + mPositionX;
+ mContainerPositionY = coords[1] + mPositionY;
+ mContainer.showAtLocation(mParent, 0, mContainerPositionX, mContainerPositionY);
+
+ // Hide paste view when handle is moved on screen.
+ if (mPastePopupWindow != null) {
+ mPastePopupWindow.hide();
+ }
+ }
+
+ void hide() {
+ mIsDragging = false;
+ mContainer.dismiss();
+ if (mPastePopupWindow != null) {
+ mPastePopupWindow.hide();
+ }
+ }
+
+ boolean isShowing() {
+ return mContainer.isShowing();
+ }
+
+ private boolean isPositionVisible() {
+ // Always show a dragging handle.
+ if (mIsDragging) {
+ return true;
+ }
+
+ final Rect clip = mTempRect;
+ clip.left = 0;
+ clip.top = 0;
+ clip.right = mParent.getWidth();
+ clip.bottom = mParent.getHeight();
+
+ final ViewParent parent = mParent.getParent();
+ if (parent == null || !parent.getChildVisibleRect(mParent, clip, null)) {
+ return false;
+ }
+
+ final int[] coords = mTempCoords;
+ mParent.getLocationInWindow(coords);
+ final int posX = coords[0] + mPositionX + (int) mHotspotX;
+ final int posY = coords[1] + mPositionY + (int) mHotspotY;
+
+ return posX >= clip.left && posX <= clip.right &&
+ posY >= clip.top && posY <= clip.bottom;
+ }
+
+ void moveTo(int x, int y) {
+ mPositionX = x;
+ mPositionY = y;
+ if (isPositionVisible()) {
+ int[] coords = null;
+ if (mContainer.isShowing()) {
+ coords = mTempCoords;
+ mParent.getLocationInWindow(coords);
+ final int containerPositionX = coords[0] + mPositionX;
+ final int containerPositionY = coords[1] + mPositionY;
+
+ if (containerPositionX != mContainerPositionX ||
+ containerPositionY != mContainerPositionY) {
+ mContainerPositionX = containerPositionX;
+ mContainerPositionY = containerPositionY;
+
+ mContainer.update(mContainerPositionX, mContainerPositionY,
+ getRight() - getLeft(), getBottom() - getTop());
+
+ // Hide paste popup window as soon as a scroll occurs.
+ if (mPastePopupWindow != null) {
+ mPastePopupWindow.hide();
+ }
+ }
+ } else {
+ show();
+ }
+
+ if (mIsDragging) {
+ if (coords == null) {
+ coords = mTempCoords;
+ mParent.getLocationInWindow(coords);
+ }
+ if (coords[0] != mLastParentX || coords[1] != mLastParentY) {
+ mTouchToWindowOffsetX += coords[0] - mLastParentX;
+ mTouchToWindowOffsetY += coords[1] - mLastParentY;
+ mLastParentX = coords[0];
+ mLastParentY = coords[1];
+ }
+ // Hide paste popup window as soon as the handle is dragged.
+ if (mPastePopupWindow != null) {
+ mPastePopupWindow.hide();
+ }
+ }
+ } else {
+ hide();
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas c) {
+ mDrawable.setBounds(0, 0, getRight() - getLeft(), getBottom() - getTop());
+ mDrawable.draw(c);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ switch (ev.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN: {
+ mDownPositionX = ev.getRawX();
+ mDownPositionY = ev.getRawY();
+ mTouchToWindowOffsetX = mDownPositionX - mPositionX;
+ mTouchToWindowOffsetY = mDownPositionY - mPositionY;
+ final int[] coords = mTempCoords;
+ mParent.getLocationInWindow(coords);
+ mLastParentX = coords[0];
+ mLastParentY = coords[1];
+ mIsDragging = true;
+ mController.beforeStartUpdatingPosition(this);
+ mTouchTimer = SystemClock.uptimeMillis();
+ break;
+ }
+
+ case MotionEvent.ACTION_MOVE: {
+ updatePosition(ev.getRawX(), ev.getRawY());
+ break;
+ }
+
+ case MotionEvent.ACTION_UP:
+ if (mIsInsertionHandle) {
+ long delay = SystemClock.uptimeMillis() - mTouchTimer;
+ if (delay < ViewConfiguration.getTapTimeout()) {
+ if (mPastePopupWindow != null && mPastePopupWindow.isShowing()) {
+ // Tapping on the handle dismisses the displayed paste view,
+ mPastePopupWindow.hide();
+ } else {
+ showPastePopupWindow();
+ }
+ }
+ }
+ mIsDragging = false;
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ mIsDragging = false;
+ break;
+ }
+ return true;
+ }
+
+ boolean isDragging() {
+ return mIsDragging;
+ }
+
+ /**
+ * @return Returns the x position of the handle
+ */
+ int getPositionX() {
+ return mPositionX;
+ }
+
+ /**
+ * @return Returns the y position of the handle
+ */
+ int getPositionY() {
+ return mPositionY;
+ }
+
+ private void updatePosition(float rawX, float rawY) {
+ final float newPosX = rawX - mTouchToWindowOffsetX + mHotspotX;
+ final float newPosY = rawY - mTouchToWindowOffsetY + mHotspotY - mLineOffsetY;
+
+ mController.updatePosition(this, Math.round(newPosX), Math.round(newPosY));
+ }
+
+ void positionAt(int x, int y) {
+ moveTo((int)(x - mHotspotX), (int)(y - mHotspotY));
+ }
+
+ // Returns the x coordinate of the position that the handle appears to be pointing to.
+ int getAdjustedPositionX() {
+ return (int) (mPositionX + mHotspotX);
+ }
+
+ // Returns the y coordinate of the position that the handle appears to be pointing to.
+ int getAdjustedPositionY() {
+ return (int) (mPositionY + mHotspotY);
+ }
+
+ // Returns a suitable y coordinate for the text position corresponding to the handle.
+ // As the handle points to a position on the base of the line of text, this method
+ // returns a coordinate a small number of pixels higher (i.e. a slightly smaller number)
+ // than getAdjustedPositionY.
+ int getLineAdjustedPositionY() {
+ return (int) (mPositionY + mHotspotY - mLineOffsetY);
+ }
+
+ Drawable getDrawable() {
+ return mDrawable;
+ }
+
+ void showPastePopupWindow() {
+ InsertionHandleController ihc = (InsertionHandleController) mController;
+ if (mIsInsertionHandle && ihc.canPaste()) {
+ if (mPastePopupWindow == null) {
+ // Lazy initialization: create when actually shown only.
+ mPastePopupWindow = ihc.new PastePopupMenu();
+ }
+ mPastePopupWindow.show();
+ }
+ }
+}
diff --git a/content/public/android/java/src/org/chromium/content/browser/InsertionHandleController.java b/content/public/android/java/src/org/chromium/content/browser/InsertionHandleController.java
index 8d84fde..2dd4008 100644
--- a/content/public/android/java/src/org/chromium/content/browser/InsertionHandleController.java
+++ b/content/public/android/java/src/org/chromium/content/browser/InsertionHandleController.java
@@ -4,14 +4,35 @@
package org.chromium.content.browser;
+import android.content.ClipboardManager;
import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.view.Gravity;
+import android.view.LayoutInflater;
import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.PopupWindow;
-class InsertionHandleController {
+/**
+ * CursorController for inserting text at the cursor position.
+ */
+abstract class InsertionHandleController implements CursorController {
+
+ /** The handle view, lazily created when first shown */
+ private HandleView mHandle;
/** The view over which the insertion handle should be shown */
private View mParent;
+ /** True iff the insertion handle is currently showing */
+ private boolean mIsShowing;
+
+ /** True iff the insertion handle can be shown automatically when selection changes */
+ private boolean mAllowAutomaticShowing;
+
private Context mContext;
InsertionHandleController(View parent) {
@@ -19,8 +40,251 @@ class InsertionHandleController {
mContext = parent.getContext();
}
+ /** Allows the handle to be shown automatically when cursor position changes */
+ void allowAutomaticShowing() {
+ mAllowAutomaticShowing = true;
+ }
+
/** Disallows the handle from being shown automatically when cursor position changes */
- public void hideAndDisallowAutomaticShowing() {
- // TODO(olilan): add method implementation (this is just a stub for ImeAdapter).
+ void hideAndDisallowAutomaticShowing() {
+ hide();
+ mAllowAutomaticShowing = false;
+ }
+
+ /**
+ * Sets the position and shows the handle.
+ * @param x1
+ * @param y1
+ */
+ void showHandleAt(int x, int y) {
+ createHandleIfNeeded();
+ setHandlePosition(x, y);
+ showHandleIfNeeded();
+ }
+
+ void showPastePopup() {
+ if (mIsShowing) {
+ mHandle.showPastePopupWindow();
+ }
+ }
+
+ void showHandleWithPastePopupAt(int x, int y) {
+ showHandleAt(x, y);
+ showPastePopup();
+ }
+
+ /** Shows the handle at the given coordinates, as long as automatic showing is allowed */
+ void onCursorPositionChanged(int x, int y) {
+ if (mAllowAutomaticShowing) {
+ showHandleAt(x, y);
+ }
+ }
+
+ /**
+ * Moves the handle so that it points at the given coordinates.
+ * @param x
+ * @param y
+ */
+ void setHandlePosition(int x, int y) {
+ mHandle.positionAt(x, y);
+ }
+
+ @Override
+ public void onTouchModeChanged(boolean isInTouchMode) {
+ if (!isInTouchMode) {
+ hide();
+ }
+ }
+
+ @Override
+ public void hide() {
+ if (mIsShowing) {
+ if (mHandle != null) mHandle.hide();
+ mIsShowing = false;
+ }
+ }
+
+ @Override
+ public boolean isShowing() {
+ return mIsShowing;
+ }
+
+ @Override
+ public void beforeStartUpdatingPosition(HandleView handle) {}
+
+ @Override
+ public void updatePosition(HandleView handle, int x, int y) {
+ setCursorPosition(x, y);
+ }
+
+ /**
+ * The concrete implementation must cause the cursor position to move to the given
+ * coordinates and (possibly asynchronously) set the insertion handle position
+ * after the cursor position change is made via showHandleAt(x,y).
+ * @param x
+ * @param y
+ */
+ protected abstract void setCursorPosition(int x, int y);
+
+ /** Pastes the contents of clipboard at the current insertion point */
+ protected abstract void paste();
+
+ /** Returns the current line height in pixels */
+ protected abstract int getLineHeight();
+
+ @Override
+ public void onDetached() {}
+
+ boolean canPaste() {
+ return ((ClipboardManager)mContext.getSystemService(
+ Context.CLIPBOARD_SERVICE)).hasPrimaryClip();
+ }
+
+ private void createHandleIfNeeded() {
+ if (mHandle == null) mHandle = new HandleView(this, HandleView.CENTER, mParent);
+ }
+
+ private void showHandleIfNeeded() {
+ if (!mIsShowing) {
+ mIsShowing = true;
+ mHandle.show();
+ }
+ }
+
+ /*
+ * This class is based on TextView.PastePopupMenu.
+ */
+ class PastePopupMenu implements OnClickListener {
+ private final PopupWindow mContainer;
+ private int mPositionX;
+ private int mPositionY;
+ private View[] mPasteViews;
+ private int[] mPasteViewLayouts;
+
+ public PastePopupMenu() {
+ mContainer = new PopupWindow(mContext, null,
+ android.R.attr.textSelectHandleWindowStyle);
+ mContainer.setSplitTouchEnabled(true);
+ mContainer.setClippingEnabled(false);
+
+ mContainer.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
+ mContainer.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
+
+ final int[] POPUP_LAYOUT_ATTRS = {
+ android.R.attr.textEditPasteWindowLayout,
+ android.R.attr.textEditNoPasteWindowLayout,
+ android.R.attr.textEditSidePasteWindowLayout,
+ android.R.attr.textEditSideNoPasteWindowLayout,
+ };
+
+ mPasteViews = new View[POPUP_LAYOUT_ATTRS.length];
+ mPasteViewLayouts = new int[POPUP_LAYOUT_ATTRS.length];
+
+ TypedArray attrs = mContext.obtainStyledAttributes(POPUP_LAYOUT_ATTRS);
+ for (int i = 0; i < attrs.length(); ++i) {
+ mPasteViewLayouts[i] = attrs.getResourceId(attrs.getIndex(i), 0);
+ }
+ attrs.recycle();
+ }
+
+ private int viewIndex(boolean onTop) {
+ return (onTop ? 0 : 1<<1) + (canPaste() ? 0 : 1 << 0);
+ }
+
+ private void updateContent(boolean onTop) {
+ final int viewIndex = viewIndex(onTop);
+ View view = mPasteViews[viewIndex];
+
+ if (view == null) {
+ final int layout = mPasteViewLayouts[viewIndex];
+ LayoutInflater inflater = (LayoutInflater)mContext.
+ getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ if (inflater != null) {
+ view = inflater.inflate(layout, null);
+ }
+
+ if (view == null) {
+ throw new IllegalArgumentException("Unable to inflate TextEdit paste window");
+ }
+
+ final int size = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+ view.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ view.measure(size, size);
+
+ view.setOnClickListener(this);
+
+ mPasteViews[viewIndex] = view;
+ }
+
+ mContainer.setContentView(view);
+ }
+
+ void show() {
+ updateContent(true);
+ positionAtCursor();
+ }
+
+ void hide() {
+ mContainer.dismiss();
+ }
+
+ boolean isShowing() {
+ return mContainer.isShowing();
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (canPaste()) {
+ paste();
+ }
+ hide();
+ }
+
+ void positionAtCursor() {
+ View contentView = mContainer.getContentView();
+ int width = contentView.getMeasuredWidth();
+ int height = contentView.getMeasuredHeight();
+
+ int lineHeight = getLineHeight();
+
+ mPositionX = (int) (mHandle.getAdjustedPositionX() - width / 2.0f);
+ mPositionY = mHandle.getAdjustedPositionY() - height - lineHeight;
+
+ final int[] coords = new int[2];
+ mParent.getLocationInWindow(coords);
+ coords[0] += mPositionX;
+ coords[1] += mPositionY;
+
+ final int screenWidth = mContext.getResources().getDisplayMetrics().widthPixels;
+ if (coords[1] < 0) {
+ updateContent(false);
+ // Update dimensions from new view
+ contentView = mContainer.getContentView();
+ width = contentView.getMeasuredWidth();
+ height = contentView.getMeasuredHeight();
+
+ // Vertical clipping, move under edited line and to the side of insertion cursor
+ // TODO bottom clipping in case there is no system bar
+ coords[1] += height;
+ coords[1] += lineHeight;
+
+ // Move to right hand side of insertion cursor by default. TODO RTL text.
+ final Drawable handle = mHandle.getDrawable();
+ final int handleHalfWidth = handle.getIntrinsicWidth() / 2;
+
+ if (mHandle.getAdjustedPositionX() + width < screenWidth) {
+ coords[0] += handleHalfWidth + width / 2;
+ } else {
+ coords[0] -= handleHalfWidth + width / 2;
+ }
+ } else {
+ // Horizontal clipping
+ coords[0] = Math.max(0, coords[0]);
+ coords[0] = Math.min(screenWidth - width, coords[0]);
+ }
+
+ mContainer.showAtLocation(mParent, Gravity.NO_GRAVITY, coords[0], coords[1]);
+ }
}
}
diff --git a/content/public/android/java/src/org/chromium/content/browser/SelectionHandleController.java b/content/public/android/java/src/org/chromium/content/browser/SelectionHandleController.java
index 8e6b0bf..f89332a 100644
--- a/content/public/android/java/src/org/chromium/content/browser/SelectionHandleController.java
+++ b/content/public/android/java/src/org/chromium/content/browser/SelectionHandleController.java
@@ -6,16 +6,192 @@ package org.chromium.content.browser;
import android.view.View;
-class SelectionHandleController {
+/**
+ * CursorController for selecting a range of text.
+ */
+abstract class SelectionHandleController implements CursorController {
+
+ // The following constants match the ones in base/i18n/rtl.h.
+ private static final int TEXT_DIRECTION_RTL = 1;
+ private static final int TEXT_DIRECTION_LTR = 2;
+
+ /** The cursor controller images, lazily created when shown. */
+ private HandleView mStartHandle, mEndHandle;
+
+ /** Whether handles should show automatically when text is selected. */
+ private boolean mAllowAutomaticShowing = true;
+
+ /** Whether selection anchors are active. */
+ private boolean mIsShowing;
private View mParent;
+ private int mFixedHandleX;
+ private int mFixedHandleY;
+
SelectionHandleController(View parent) {
mParent = parent;
}
+ /** Automatically show selection anchors when text is selected. */
+ void allowAutomaticShowing() {
+ mAllowAutomaticShowing = true;
+ }
+
/** Hide selection anchors, and don't automatically show them. */
- public void hideAndDisallowAutomaticShowing() {
- // TODO(olilan): add method implementation (this is just a stub for ImeAdapter).
+ void hideAndDisallowAutomaticShowing() {
+ hide();
+ mAllowAutomaticShowing = false;
+ }
+
+ @Override
+ public boolean isShowing() {
+ return mIsShowing;
+ }
+
+ @Override
+ public void hide() {
+ if (mIsShowing) {
+ if (mStartHandle != null) mStartHandle.hide();
+ if (mEndHandle != null) mEndHandle.hide();
+ mIsShowing = false;
+ }
+ }
+
+ void cancelFadeOutAnimation() {
+ hide();
+ }
+
+ /**
+ * Updates the selection for a movement of the given handle (which
+ * should be the start handle or end handle) to coordinates x,y.
+ * Note that this will not actually result in the handle moving to (x,y):
+ * selectBetweenCoordinates(x1,y1,x2,y2) will trigger the selection and set the
+ * actual coordinates later via showHandlesAt.
+ */
+ @Override
+ public void updatePosition(HandleView handle, int x, int y) {
+ selectBetweenCoordinates(mFixedHandleX, mFixedHandleY, x, y);
+ }
+
+ @Override
+ public void beforeStartUpdatingPosition(HandleView handle) {
+ HandleView fixedHandle = (handle == mStartHandle) ? mEndHandle : mStartHandle;
+ mFixedHandleX = fixedHandle.getAdjustedPositionX();
+ mFixedHandleY = fixedHandle.getLineAdjustedPositionY();
+ }
+
+ /**
+ * The concrete implementation must trigger a selection between the given
+ * coordinates and (possibly asynchronously) set the actual handle positions
+ * after the selection is made via showHandlesAt(x1,y1,x2,y2).
+ * @param x1
+ * @param y1
+ * @param x2
+ * @param y2
+ */
+ protected abstract void selectBetweenCoordinates(int x1, int y1, int x2, int y2);
+
+ /**
+ * @return true iff this controller is being used to move the selection start.
+ */
+ boolean isSelectionStartDragged() {
+ return mStartHandle != null && mStartHandle.isDragging();
+ }
+
+ /**
+ * @return true iff this controller is being used to drag either the selection start or end.
+ */
+ boolean isDragging() {
+ return (mStartHandle != null && mStartHandle.isDragging()) ||
+ (mEndHandle != null && mEndHandle.isDragging());
+ }
+
+ @Override
+ public void onTouchModeChanged(boolean isInTouchMode) {
+ if (!isInTouchMode) {
+ hide();
+ }
+ }
+
+ @Override
+ public void onDetached() {}
+
+ /**
+ * Moves the start handle so that it points at the given coordinates.
+ * @param x
+ * @param y
+ */
+ void setStartHandlePosition(int x, int y) {
+ mStartHandle.positionAt(x, y);
+ }
+
+ /**
+ * Moves the end handle so that it points at the given coordinates.
+ * @param x
+ * @param y
+ */
+ void setEndHandlePosition(int x, int y) {
+ mEndHandle.positionAt(x, y);
+ }
+
+ /**
+ * Moves the start handle by x pixels horizontally and y pixels vertically.
+ * @param x
+ * @param y
+ */
+ void moveStartHandleBy(int x, int y) {
+ mStartHandle.moveTo(mStartHandle.getPositionX() + x, mStartHandle.getPositionY() + y);
+ }
+
+ /**
+ * Moves the end handle by x pixels horizontally and y pixels vertically.
+ * @param x
+ * @param y
+ */
+ void moveEndHandleBy(int x, int y) {
+ mEndHandle.moveTo(mEndHandle.getPositionX() + x, mEndHandle.getPositionY() + y);
+ }
+
+ void onSelectionChanged(int x1, int y1, int dir1, int x2, int y2, int dir2) {
+ if (mAllowAutomaticShowing) {
+ showHandlesAt(x1, y1, dir1, x2, y2, dir2);
+ }
+ }
+
+ /**
+ * Sets both start and end position and show the handles.
+ * Note: this method does not trigger a selection, see
+ * selectBetweenCoordinates()
+ *
+ * @param x1
+ * @param y1
+ * @param x2
+ * @param y2
+ */
+ void showHandlesAt(int x1, int y1, int dir1, int x2, int y2, int dir2) {
+ createHandlesIfNeeded(dir1, dir2);
+ setStartHandlePosition(x1, y1);
+ setEndHandlePosition(x2, y2);
+ showHandlesIfNeeded();
+ }
+
+ private void createHandlesIfNeeded(int start_dir, int end_dir) {
+ if (mStartHandle == null) {
+ mStartHandle = new HandleView(this,
+ start_dir == TEXT_DIRECTION_LTR ? HandleView.LEFT : HandleView.RIGHT, mParent);
+ }
+ if (mEndHandle == null) {
+ mEndHandle = new HandleView(this,
+ end_dir == TEXT_DIRECTION_LTR ? HandleView.RIGHT : HandleView.LEFT, mParent);
+ }
+ }
+
+ private void showHandlesIfNeeded() {
+ if (!mIsShowing) {
+ mIsShowing = true;
+ mStartHandle.show();
+ mEndHandle.show();
+ }
}
}