@@ -24,6 +24,7 @@ import android.content.Context
2424import android.content.res.TypedArray
2525import android.graphics.Bitmap
2626import android.graphics.Canvas
27+ import android.graphics.Rect
2728import android.graphics.drawable.BitmapDrawable
2829import android.graphics.drawable.Drawable
2930import android.graphics.drawable.VectorDrawable
@@ -47,6 +48,7 @@ import android.view.KeyEvent
4748import android.view.LayoutInflater
4849import android.view.MotionEvent
4950import android.view.View
51+ import android.view.View.OnLongClickListener
5052import android.view.WindowManager
5153import android.view.inputmethod.BaseInputConnection
5254import android.widget.CheckBox
@@ -86,6 +88,7 @@ import org.wordpress.aztec.spans.AztecMediaClickableSpan
8688import org.wordpress.aztec.spans.AztecMediaSpan
8789import org.wordpress.aztec.spans.AztecURLSpan
8890import org.wordpress.aztec.spans.AztecVideoSpan
91+ import org.wordpress.aztec.spans.AztecVisualLinebreak
8992import org.wordpress.aztec.spans.CommentSpan
9093import org.wordpress.aztec.spans.EndOfParagraphMarker
9194import org.wordpress.aztec.spans.IAztecAttributedSpan
@@ -299,6 +302,9 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
299302
300303 val contentChangeWatcher = AztecContentChangeWatcher ()
301304
305+ var lastPressedXCoord: Int = 0
306+ var lastPressedYCoord: Int = 0
307+
302308 interface OnSelectionChangedListener {
303309 fun onSelectionChanged (selStart : Int , selEnd : Int )
304310 }
@@ -466,11 +472,112 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
466472 // Needed to properly initialize the cursor position
467473 setSelection(0 )
468474
475+ // only override startDrag for Android 9 or later to workaround the following issue:
476+ // "IllegalStateException: Drag shadow dimensions must be positive"
477+ // - see https://issuetracker.google.com/issues/113347222
478+ // - also https://github.com/wordpress-mobile/WordPress-Android/issues/10492
479+ // rationale: the LongClick gesture takes precedence over the startDrag operation
480+ // so, listening to it first gives us the possibility to discard processing the event
481+ // when the crash conditions would be otherwise met. Conditions follow.
482+ // In the case of a zero width character being the sole selection, the shadow dimensions
483+ // would be zero, incurring in the actual crash. Given it doesn't really make sense to
484+ // select a newline and try dragging it around, we're just completely capturing the event
485+ // and signaling the OS that it was handled, so it doesn't propagate to the TextView's
486+ // longClickListener actually implementing dragging.
487+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .P ) {
488+ setOnLongClickListener(OnLongClickListener { // if we have a selection
489+ val start: Int = getSelectionStart()
490+ val end: Int = getSelectionEnd()
491+ // the selection is exactly 1 character long, so let's check
492+ if (end - start == 1 ) {
493+ // check if the user long-clicked on the selection to start a drag movement
494+ val selectionRect: Rect = getBoxContainingSelectionCoordinates()
495+ if (selectionRect.left < lastPressedXCoord
496+ && selectionRect.top < lastPressedYCoord
497+ && selectionRect.right > lastPressedXCoord
498+ && selectionRect.bottom > lastPressedYCoord) {
499+
500+ if (selectionHasExactlyOneMarker(
501+ start,
502+ end,
503+ EndOfParagraphMarker ::class .java)) {
504+ // signal this event as handled so dragging does not go forward
505+ return @OnLongClickListener true
506+ }
507+
508+ if (selectionHasExactlyOneMarker(
509+ start,
510+ end,
511+ AztecVisualLinebreak ::class .java)) {
512+ // signal this event as handled so dragging does not go forward
513+ return @OnLongClickListener true
514+ }
515+ }
516+ }
517+ false
518+ })
519+ }
520+
469521 enableTextChangedListener()
470522
471523 isViewInitialized = true
472524 }
473525
526+ private fun <T >selectionHasExactlyOneMarker (start : Int , end : Int , type : Class <T >): Boolean {
527+ val spanFound: Array <T > = editableText.getSpans(
528+ start,
529+ end,
530+ type
531+ )
532+ return spanFound.size == 1
533+ }
534+
535+ private fun getBoxContainingSelectionCoordinates (): Rect {
536+ // obtain the location on the screen, we'll use it later to adjust x/y
537+ val location = IntArray (2 )
538+ getLocationOnScreen(location)
539+ val startLine = layout.getLineForOffset(selectionStart)
540+ val endLine = layout.getLineForOffset(selectionEnd)
541+ val startLineBounds = Rect ()
542+ getLineBounds(startLine, startLineBounds)
543+ val containingBoxBounds: Rect
544+ // if both lines aren't the same, the selection expands accross multiple lines
545+ containingBoxBounds = if (endLine != startLine) {
546+ // in such case, let's simplify things and obtain the bigger box
547+ // (first line top/left, last line bottom/right)
548+ val lastLineBounds = Rect ()
549+ getLineBounds(endLine, lastLineBounds)
550+ Rect (
551+ startLineBounds.left + location[0 ] - scrollX,
552+ startLineBounds.top + location[1 ] - scrollY,
553+ lastLineBounds.right + location[0 ] - scrollX,
554+ lastLineBounds.bottom + location[1 ] - scrollY
555+ )
556+ } else {
557+ // if the selection doesn't go through lines, then make the containing box adjusted to actual
558+ // selection start / end
559+ // now I need the X to be the actual start cursor X
560+ val left = (layout.getPrimaryHorizontal(selectionStart).toInt() + location[0 ]
561+ - scrollX + startLineBounds.left)
562+ val right = (layout.getPrimaryHorizontal(selectionEnd).toInt() + location[0 ]
563+ - scrollX + startLineBounds.left)
564+ val top = startLineBounds.top + location[1 ] - scrollY
565+ val bottom = startLineBounds.bottom + location[1 ] - scrollY
566+ Rect (left, top, right, bottom)
567+ }
568+ return containingBoxBounds
569+ }
570+
571+ override fun onTouchEvent (event : MotionEvent ): Boolean {
572+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .P
573+ && event.action == MotionEvent .ACTION_DOWN ) {
574+ // we'll use these values in OnLongClickListener
575+ lastPressedXCoord = event.rawX.toInt()
576+ lastPressedYCoord = event.rawY.toInt()
577+ }
578+ return super .onTouchEvent(event)
579+ }
580+
474581 // Setup the keyListener(s) for Backspace and Enter key.
475582 // Backspace: If listener does return false we remove the style here
476583 // Enter: Ask the listener if we need to insert or not the char
0 commit comments