Before posting this question, I searched a lot, but could not find clear answers to this problem.
I need to override the default text selection in the android web browser and show the options for the custom text selection dialog. I tried this sample code project .
This sample project works on the following devices and emulator:
- Acer Iconia a500 tablet: 10 inch: Android OS - 3.0
- Acer Iconia a500 tablet: 10 inch: Android OS - 3.2
- Samsung Galaxy Tab: 10 inches: Android OS - 4.0
- Samsung Galaxy Tab: 7 inches: Android OS - 4.0
- Emulator: Skin-WVGA800: Android OS - 4.1.2
Does not work on the following devices:
- Samsung Galaxy Tab: 10 inches: Android OS - 4.1.2
- Samsung Galaxy Tab: 7 inches: Android OS - 4.1.2
In android os versions 4.1 and 4.1+, instead of showing a dialog for selecting custom text, the default Android action bar for selecting text is displayed on the screen.
I searched a lot in this question, many suggested using the onLongClick () interface method
I already asked a question on this forum, see the link with answers to these questions. I can clone the onLongClick () event, but I cannot stop the default action bar for the text.
I have few questions for this scenario.
1. Why does the onLongClick () method stop working on a device running android os version 4.1+?
2.How to stop the default text selection bar by long clicking on the text from the web view?
This is my custom webview class.
package com.epubreader.ebook; import org.json.JSONException; import org.json.JSONObject; import android.app.Activity; import android.content.Context; import android.graphics.Rect; import android.graphics.Region; import android.os.Handler; import android.os.Message; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.view.ContextMenu; import android.view.Display; import android.view.GestureDetector; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnLongClickListener; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.WindowManager; import android.webkit.WebView; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.Toast; import com.epubreader.R; import com.epubreader.drag.DragController; import com.epubreader.drag.DragLayer; import com.epubreader.drag.DragListener; import com.epubreader.drag.DragSource; import com.epubreader.drag.MyAbsoluteLayout; import com.epubreader.menu.menuAnimationHelper; import com.epubreader.textselection.WebTextSelectionJSInterface; import com.epubreader.textselectionoverlay.ActionItem; import com.epubreader.textselectionoverlay.QuickAction; import com.epubreader.textselectionoverlay.QuickAction.OnDismissListener; public class CustomWebView extends WebView implements WebTextSelectionJSInterface, OnTouchListener , OnLongClickListener, OnDismissListener, DragListener{ /** The logging tag. */ private static final String TAG = "CustomWebView"; /** Context. */ protected Context ctx; /** The context menu. */ private QuickAction mContextMenu; /** The drag layer for selection. */ private DragLayer mSelectionDragLayer; /** The drag controller for selection. */ private DragController mDragController; /** The start selection handle. */ private ImageView mStartSelectionHandle; /** the end selection handle. */ private ImageView mEndSelectionHandle; /** The selection bounds. */ private Rect mSelectionBounds = null; /** The previously selected region. */ protected Region lastSelectedRegion = null; /** The selected range. */ protected String selectedRange = ""; /** The selected text. */ protected String selectedText = ""; /** Javascript interface for catching text selection. */ /** Selection mode flag. */ protected boolean inSelectionMode = false; /** Flag to stop from showing context menu twice. */ protected boolean contextMenuVisible = false; /** The current content width. */ protected int contentWidth = 0; /** Identifier for the selection start handle. */ private final int SELECTION_START_HANDLE = 0; /** Identifier for the selection end handle. */ private final int SELECTION_END_HANDLE = 1; /** Last touched selection handle. */ private int mLastTouchedSelectionHandle = -1; /** Variables for Left & Right Menu ***/ private View menuView; private LinearLayout toiLay; private menuAnimationHelper _menuAnimationHelper; private TocTranslateAnimation _tocTranslateAnimation; private CustomWebView _customWebView; public CustomWebView(Context context) { super(context); this.ctx = context; this.setup(context); } public CustomWebView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); this.ctx = context; this.setup(context); } public CustomWebView(Context context, AttributeSet attrs) { super(context, attrs); this.ctx = context; this.setup(context); } //***************************************************** //* //* Touch Listeners //* //***************************************************** private boolean mScrolling = false; private float mScrollDiffY = 0; private float mLastTouchY = 0; private float mScrollDiffX = 0; private float mLastTouchX = 0; @Override public boolean onTouch(View v, MotionEvent event) { float xPoint = getDensityIndependentValue(event.getX(), ctx) / getDensityIndependentValue(this.getScale(), ctx); float yPoint = getDensityIndependentValue(event.getY(), ctx) / getDensityIndependentValue(this.getScale(), ctx); // TODO: Need to update this to use this.getScale() as a factor. //Log.d(TAG, "onTouch " + xPoint + " , " + yPoint); closeMenu(); if(event.getAction() == MotionEvent.ACTION_DOWN){ final String startTouchUrl = String.format("javascript:android.selection.startTouch(%f, %f);", xPoint, yPoint); mLastTouchX = xPoint; mLastTouchY = yPoint; ((Activity)this.ctx).runOnUiThread(new Runnable() { @Override public void run() { loadUrl(startTouchUrl); } }); // This two line clones the onLongClick() longClickHandler.removeCallbacks(longClickRunnable); longClickHandler.postDelayed(longClickRunnable,300); } else if(event.getAction() == MotionEvent.ACTION_UP){ // Check for scrolling flag if(!mScrolling){ this.endSelectionMode(); } // This line clones the onLongClick() longClickHandler.removeCallbacks(longClickRunnable); mScrollDiffX = 0; mScrollDiffY = 0; mScrolling = false; }else if(event.getAction() == MotionEvent.ACTION_MOVE){ mScrollDiffX += (xPoint - mLastTouchX); mScrollDiffY += (yPoint - mLastTouchY); mLastTouchX = xPoint; mLastTouchY = yPoint; // Only account for legitimate movement. if(Math.abs(mScrollDiffX) > 10 || Math.abs(mScrollDiffY) > 10){ mScrolling = true; } // This line clones the onLongClick() longClickHandler.removeCallbacks(longClickRunnable); } // If this is in selection mode, then nothing else should handle this touch return false; } /** * Pass References of Left & Right Menu */ public void initMenu(LinearLayout _toiLay,View _menuView,menuAnimationHelper menuAnimationHelper, TocTranslateAnimation tocTranslateAnimation){ toiLay = _toiLay; menuView = _menuView; _menuAnimationHelper = menuAnimationHelper; _tocTranslateAnimation = tocTranslateAnimation; } private void closeMenu(){ if(_menuAnimationHelper != null && _menuAnimationHelper.isMenuOpenBool){ _menuAnimationHelper.close(menuView); _menuAnimationHelper.isMenuOpenBool = false; } if(_tocTranslateAnimation != null && _tocTranslateAnimation.isTocListOpenBool){ _tocTranslateAnimation.close(toiLay); _tocTranslateAnimation.isTocListOpenBool = false; } } public void removeOverlay(){ Log.d("JsHandler", "in java removeOverlay" + mScrolling); this.endSelectionMode(); mScrollDiffX = 0; mScrollDiffY = 0; mScrolling = false; } @Override public boolean onLongClick(View v){ Log.d(TAG, "from webView onLongClick "); mScrolling = true; ((Activity)this.ctx).runOnUiThread(new Runnable() { @Override public void run() { loadUrl("javascript:android.selection.longTouch()"); } }); Toast.makeText(ctx, "Long click is clicked ", Toast.LENGTH_LONG).show(); // Don't let the webview handle it return true; } //***************************************************** //* //* Setup //* //***************************************************** ContextMenu.ContextMenuInfo contextMenuInfo; /** * Setups up the web view. * @param context */ protected void setup(Context context){ // On Touch Listener this.setOnTouchListener(this); this.setClickable(false); this.setLongClickable(true); this.setOnLongClickListener(this); contextMenuInfo = this.getContextMenuInfo(); // Webview setup this.getSettings().setJavaScriptEnabled(true); this.getSettings().setJavaScriptCanOpenWindowsAutomatically(true); // Create the selection handles createSelectionLayer(context); // Set to the empty region Region region = new Region(); region.setEmpty(); _customWebView = this; this.lastSelectedRegion = region; } /** * To clone OnLongClick Listener because its not responding for version 4.1 */ public Runnable longClickRunnable = new Runnable() { public void run() { longClickHandler.sendEmptyMessage(0); } }; public Handler longClickHandler = new Handler(){ public void handleMessage(Message m){ _customWebView.loadUrl("javascript:android.selection.longTouch();"); mScrolling = true; } }; public WebTextSelectionJSInterface getTextSelectionJsInterface(){ return this; } //***************************************************** //* //* Selection Layer Handling //* //***************************************************** /** * Creates the selection layer. * * @param context */ protected void createSelectionLayer(Context context){ LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); this.mSelectionDragLayer = (DragLayer) inflater.inflate(R.layout.selection_drag_layer, null); // Make sure it filling parent this.mDragController = new DragController(context); this.mDragController.setDragListener(this); this.mDragController.addDropTarget(mSelectionDragLayer); this.mSelectionDragLayer.setDragController(mDragController); this.mStartSelectionHandle = (ImageView) this.mSelectionDragLayer.findViewById(R.id.startHandle); this.mStartSelectionHandle.setTag(new Integer(SELECTION_START_HANDLE)); this.mEndSelectionHandle = (ImageView) this.mSelectionDragLayer.findViewById(R.id.endHandle); this.mEndSelectionHandle.setTag(new Integer(SELECTION_END_HANDLE)); OnTouchListener handleTouchListener = new OnTouchListener(){ @Override public boolean onTouch(View v, MotionEvent event) { boolean handledHere = false; final int action = event.getAction(); // Down event starts drag for handle. if (action == MotionEvent.ACTION_DOWN) { handledHere = startDrag (v); mLastTouchedSelectionHandle = (Integer) v.getTag(); } return handledHere; } }; this.mStartSelectionHandle.setOnTouchListener(handleTouchListener); this.mEndSelectionHandle.setOnTouchListener(handleTouchListener); } /** * Starts selection mode on the UI thread */ private Handler startSelectionModeHandler = new Handler(){ public void handleMessage(Message m){ if(mSelectionBounds == null) return; addView(mSelectionDragLayer); drawSelectionHandles(); int contentHeight = (int) Math.ceil(getDensityDependentValue(getContentHeight(), ctx)); // Update Layout Params ViewGroup.LayoutParams layerParams = mSelectionDragLayer.getLayoutParams(); layerParams.height = contentHeight; layerParams.width = contentWidth; mSelectionDragLayer.setLayoutParams(layerParams); } }; /** * Starts selection mode. * * @param selectionBounds */ public void startSelectionMode(){ this.startSelectionModeHandler.sendEmptyMessage(0); } // Ends selection mode on the UI thread private Handler endSelectionModeHandler = new Handler(){ public void handleMessage(Message m){ //Log.d("TableContentsWithDisplay", "in endSelectionModeHandler"); removeView(mSelectionDragLayer); if(getParent() != null && mContextMenu != null && contextMenuVisible){ // This will throw an error if the webview is being redrawn. // No error handling needed, just need to stop the crash. try{ mContextMenu.dismiss(); } catch(Exception e){ } } mSelectionBounds = null; mLastTouchedSelectionHandle = -1; try { ((Activity)ctx).runOnUiThread(new Runnable() { @Override public void run() { loadUrl("javascript: android.selection.clearSelection();"); } }); } catch (Exception e) { // TODO: handle exception } } }; /** * Ends selection mode. */ public void endSelectionMode(){ this.endSelectionModeHandler.sendEmptyMessage(0); } /** * Calls the handler for drawing the selection handles. */ private void drawSelectionHandles(){ this.drawSelectionHandlesHandler.sendEmptyMessage(0); } /** * Handler for drawing the selection handles on the UI thread. */ private Handler drawSelectionHandlesHandler = new Handler(){ public void handleMessage(Message m){ MyAbsoluteLayout.LayoutParams startParams = (com.epubreader.drag.MyAbsoluteLayout.LayoutParams) mStartSelectionHandle.getLayoutParams(); startParams.x = (int) (mSelectionBounds.left - mStartSelectionHandle.getDrawable().getIntrinsicWidth()); startParams.y = (int) (mSelectionBounds.top - mStartSelectionHandle.getDrawable().getIntrinsicHeight()); // Stay on screen. startParams.x = (startParams.x < 0) ? 0 : startParams.x; startParams.y = (startParams.y < 0) ? 0 : startParams.y; mStartSelectionHandle.setLayoutParams(startParams); MyAbsoluteLayout.LayoutParams endParams = (com.epubreader.drag.MyAbsoluteLayout.LayoutParams) mEndSelectionHandle.getLayoutParams(); endParams.x = (int) mSelectionBounds.right; endParams.y = (int) mSelectionBounds.bottom; endParams.x = (endParams.x < 0) ? 0 : endParams.x; endParams.y = (endParams.y < 0) ? 0 : endParams.y; mEndSelectionHandle.setLayoutParams(endParams); } }; /** * Checks to see if this view is in selection mode. * @return */ public boolean isInSelectionMode(){ return this.mSelectionDragLayer.getParent() != null; } //***************************************************** //* //* DragListener Methods //* //***************************************************** /** * Start dragging a view. * */ private boolean startDrag (View v) { // Let the DragController initiate a drag-drop sequence. // I use the dragInfo to pass along the object being dragged. // I'm not sure how the Launcher designers do this. Object dragInfo = v; mDragController.startDrag (v, mSelectionDragLayer, dragInfo, DragController.DRAG_ACTION_MOVE); return true; } @Override public void onDragStart(DragSource source, Object info, int dragAction) { // TODO Auto-generated method stub } @Override@SuppressWarnings("deprecation") public void onDragEnd() { // TODO Auto-generated method stub MyAbsoluteLayout.LayoutParams startHandleParams = (MyAbsoluteLayout.LayoutParams) this.mStartSelectionHandle.getLayoutParams(); MyAbsoluteLayout.LayoutParams endHandleParams = (MyAbsoluteLayout.LayoutParams) this.mEndSelectionHandle.getLayoutParams(); float scale = getDensityIndependentValue(this.getScale(), ctx); float startX = startHandleParams.x - this.getScrollX(); float startY = startHandleParams.y - this.getScrollY(); float endX = endHandleParams.x - this.getScrollX(); float endY = endHandleParams.y - this.getScrollY(); startX = getDensityIndependentValue(startX, ctx) / scale; startY = getDensityIndependentValue(startY, ctx) / scale; endX = getDensityIndependentValue(endX, ctx) / scale; endY = getDensityIndependentValue(endY, ctx) / scale; if(mLastTouchedSelectionHandle == SELECTION_START_HANDLE && startX > 0 && startY > 0){ final String saveStartString = String.format("javascript: android.selection.setStartPos(%f, %f);", startX, startY); ((Activity)ctx).runOnUiThread(new Runnable() { @Override public void run() { loadUrl(saveStartString); } }); } if(mLastTouchedSelectionHandle == SELECTION_END_HANDLE && endX > 0 && endY > 0){ final String saveEndString = String.format("javascript: android.selection.setEndPos(%f, %f);", endX, endY); ((Activity)ctx).runOnUiThread(new Runnable() { @Override public void run() { loadUrl(saveEndString); } }); } } //***************************************************** //* //* Context Menu Creation //* //***************************************************** /** * Shows the context menu using the given region as an anchor point. * @param region */ private void showContextMenu(Rect displayRect){ // Don't show this twice if(this.contextMenuVisible){ return; } // Don't use empty rect //if(displayRect.isEmpty()){ if(displayRect.right <= displayRect.left){ return; } //Copy action item ActionItem buttonOne = new ActionItem(); buttonOne.setTitle("HighLight"); buttonOne.setActionId(1); //buttonOne.setIcon(getResources().getDrawable(R.drawable.menu_search)); //Highlight action item ActionItem buttonTwo = new ActionItem(); buttonTwo.setTitle("Note"); buttonTwo.setActionId(2); //buttonTwo.setIcon(getResources().getDrawable(R.drawable.menu_info)); ActionItem buttonThree = new ActionItem(); buttonThree.setTitle("Help"); buttonThree.setActionId(3); //buttonThree.setIcon(getResources().getDrawable(R.drawable.menu_eraser)); // The action menu mContextMenu = new QuickAction(this.getContext()); mContextMenu.setOnDismissListener(this); // Add buttons mContextMenu.addActionItem(buttonOne); mContextMenu.addActionItem(buttonTwo); mContextMenu.addActionItem(buttonThree); //setup the action item click listener mContextMenu.setOnActionItemClickListener(new QuickAction.OnActionItemClickListener() { @Override public void onItemClick(QuickAction source, int pos, int actionId) { if (actionId == 1) { callHighLight(); } else if (actionId == 2) { callNote(); } else if (actionId == 3) { // Do Button 3 stuff Log.i(TAG, "Hit Button 3"); } contextMenuVisible = false; } }); this.contextMenuVisible = true; mContextMenu.show(this, displayRect); } private void callHighLight(){ ((Activity)this.ctx).runOnUiThread(new Runnable() { @Override public void run() { loadUrl("javascript:init_txt_selection_event()"); loadUrl("javascript:highlightme_("+0+")"); } }); } private void callNote(){ ((Activity)this.ctx).runOnUiThread(new Runnable() { @Override public void run() { loadUrl("javascript:init_txt_selection_event()"); loadUrl("javascript:fnGetUserAddedNote('1')"); } }); } //***************************************************** //* //* OnDismiss Listener //* //***************************************************** /** * Clears the selection when the context menu is dismissed. */ public void onDismiss(){ //clearSelection(); this.contextMenuVisible = false; } //***************************************************** //* //* Text Selection Javascript Interface Listener //* //***************************************************** /** * The user has started dragging the selection handles. */ public void tsjiStartSelectionMode(){ this.startSelectionMode(); } /** * The user has stopped dragging the selection handles. */ public void tsjiEndSelectionMode(){ this.endSelectionMode(); } /** * The selection has changed * @param range * @param text * @param handleBounds * @param menuBounds * @param showHighlight * @param showUnHighlight */@SuppressWarnings("deprecation") public void tsjiSelectionChanged(String range, String text, String handleBounds, String menuBounds){ try { //Log.d(TAG, "tsjiSelectionChanged :- handleBounds " + handleBounds); JSONObject selectionBoundsObject = new JSONObject(handleBounds); float scale = getDensityIndependentValue(this.getScale(), ctx); Rect handleRect = new Rect(); handleRect.left = (int) (getDensityDependentValue(selectionBoundsObject.getInt("left"), getContext()) * scale); handleRect.top = (int) (getDensityDependentValue(selectionBoundsObject.getInt("top"), getContext()) * scale); handleRect.right = (int) (getDensityDependentValue(selectionBoundsObject.getInt("right"), getContext()) * scale); handleRect.bottom = (int) (getDensityDependentValue(selectionBoundsObject.getInt("bottom"), getContext()) * scale); this.mSelectionBounds = handleRect; this.selectedRange = range; this.selectedText = text; JSONObject menuBoundsObject = new JSONObject(menuBounds); Rect displayRect = new Rect(); displayRect.left = (int) (getDensityDependentValue(menuBoundsObject.getInt("left"), getContext()) * scale); displayRect.top = (int) (getDensityDependentValue(menuBoundsObject.getInt("top") - 25, getContext()) * scale); displayRect.right = (int) (getDensityDependentValue(menuBoundsObject.getInt("right"), getContext()) * scale); displayRect.bottom = (int) (getDensityDependentValue(menuBoundsObject.getInt("bottom") + 25, getContext()) * scale); if(!this.isInSelectionMode()){ this.startSelectionMode(); } // This will send the menu rect this.showContextMenu(displayRect); drawSelectionHandles(); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * Receives the content width for the page. */ public void tsjiSetContentWidth(float contentWidth){ this.contentWidth = (int) this.getDensityDependentValue(contentWidth, ctx); } //***************************************************** //* //* Density Conversion //* //***************************************************** /** * Returns the density dependent value of the given float * @param val * @param ctx * @return */ public float getDensityDependentValue(float val, Context ctx){ // Get display from context Display display = ((WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); // Calculate min bound based on metrics DisplayMetrics metrics = new DisplayMetrics(); display.getMetrics(metrics); return val * (metrics.densityDpi / 160f); } /** * Returns the density independent value of the given float * @param val * @param ctx * @return */ public float getDensityIndependentValue(float val, Context ctx){ // Get display from context Display display = ((WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); // Calculate min bound based on metrics DisplayMetrics metrics = new DisplayMetrics(); display.getMetrics(metrics); return val / (metrics.densityDpi / 160f); }
}
Thanks in advance!