Skip to content

Commit f65933f

Browse files
authored
Merge pull request #924 from wordpress-mobile/fix/drag-shadow-dimensions-crash
Fix drag shadow dimensions crash
2 parents 77a5cb3 + 7cbbf30 commit f65933f

File tree

1 file changed

+107
-0
lines changed

1 file changed

+107
-0
lines changed

aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import android.content.Context
2424
import android.content.res.TypedArray
2525
import android.graphics.Bitmap
2626
import android.graphics.Canvas
27+
import android.graphics.Rect
2728
import android.graphics.drawable.BitmapDrawable
2829
import android.graphics.drawable.Drawable
2930
import android.graphics.drawable.VectorDrawable
@@ -47,6 +48,7 @@ import android.view.KeyEvent
4748
import android.view.LayoutInflater
4849
import android.view.MotionEvent
4950
import android.view.View
51+
import android.view.View.OnLongClickListener
5052
import android.view.WindowManager
5153
import android.view.inputmethod.BaseInputConnection
5254
import android.widget.CheckBox
@@ -86,6 +88,7 @@ import org.wordpress.aztec.spans.AztecMediaClickableSpan
8688
import org.wordpress.aztec.spans.AztecMediaSpan
8789
import org.wordpress.aztec.spans.AztecURLSpan
8890
import org.wordpress.aztec.spans.AztecVideoSpan
91+
import org.wordpress.aztec.spans.AztecVisualLinebreak
8992
import org.wordpress.aztec.spans.CommentSpan
9093
import org.wordpress.aztec.spans.EndOfParagraphMarker
9194
import 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

Comments
 (0)