Skip to content

Commit 4a04b9e

Browse files
authored
Merge pull request #801 from wordpress-mobile/issue/729-dymanic-layout-crash-preventer-take2
Android 8.x dymanic layout crash preventer
2 parents 14b9f94 + fd3284c commit 4a04b9e

File tree

3 files changed

+78
-3
lines changed

3 files changed

+78
-3
lines changed

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

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,8 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
224224
private var consumeSelectionChangedEvent: Boolean = false
225225
private var isInlineTextHandlerEnabled: Boolean = true
226226
private var bypassObservationQueue: Boolean = false
227+
private var bypassMediaDeletedListener: Boolean = false
228+
private var bypassCrashPreventerInputFilter: Boolean = false
227229

228230
var initialEditorContentParsedSHA256: ByteArray = ByteArray(0)
229231

@@ -420,7 +422,7 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
420422
// triggers ClickableSpan onClick() events
421423
movementMethod = EnhancedMovementMethod
422424

423-
setupBackspaceAndEnterDetection()
425+
setupKeyListenersAndInputFilters()
424426

425427
//disable auto suggestions/correct for older devices
426428
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
@@ -440,12 +442,52 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
440442
// Setup the keyListener(s) for Backspace and Enter key.
441443
// Backspace: If listener does return false we remove the style here
442444
// Enter: Ask the listener if we need to insert or not the char
443-
private fun setupBackspaceAndEnterDetection() {
445+
private fun setupKeyListenersAndInputFilters() {
444446
//hardware keyboard
445447
setOnKeyListener { _, _, event ->
446448
handleBackspaceAndEnter(event)
447449
}
448450

451+
// This InputFilter created only for the purpose of avoiding crash described here:
452+
// https://android-review.googlesource.com/c/platform/frameworks/base/+/634929
453+
// https://github.com/wordpress-mobile/AztecEditor-Android/issues/729
454+
// the rationale behind this workaround is that the specific crash happens only when adding/deleting text right
455+
// before an AztecImageSpan, so we detect the specific case and re-create the contents only when that happens.
456+
// This is indeed tackling the symptom rather than the actual problem, and should be removed once the real
457+
// problem is fixed at the Android OS level as described in the following url
458+
// https://android-review.googlesource.com/c/platform/frameworks/base/+/634929
459+
val dynamicLayoutCrashPreventer = InputFilter { source, start, end, dest, dstart, dend ->
460+
var temp : CharSequence? = null
461+
if (!bypassCrashPreventerInputFilter && dstart == dend && dest.length > dend+1) {
462+
// dstart == dend means this is an insertion
463+
// if there are any images right after the destination position, hack the text
464+
val spans = dest.getSpans(dstart, dend+1, AztecImageSpan::class.java)
465+
if (spans.isNotEmpty()) {
466+
// prevent this filter from running twice when `text.insert()` gets called a few lines below
467+
disableCrashPreventerInputFilter()
468+
// disable MediaDeleted listener before operating on content
469+
disableMediaDeletedListener()
470+
471+
// take the source (that is, what is being inserted), and append the Image to it. We will delete
472+
// the original Image later so to not have a duplicate.
473+
// use Spannable to copy / keep the current spans
474+
temp = SpannableStringBuilder(source).append(dest.subSequence(dend, dend+1))
475+
476+
// delete the original AztecImageSpan
477+
text.delete(dend, dend+1)
478+
// now insert both the new insertion _and_ the original AztecImageSpan
479+
text.insert(dend, temp)
480+
temp = "" // discard the original source parameter as an ouput from this InputFilter
481+
482+
// re-enable MediaDeleted listener
483+
enableMediaDeletedListener()
484+
// re-enable this very filter
485+
enableCrashPreventerInputFilter()
486+
}
487+
}
488+
temp
489+
}
490+
449491
val emptyEditTextBackspaceDetector = InputFilter { source, start, end, dest, dstart, dend ->
450492
if (selectionStart == 0 && selectionEnd == 0
451493
&& end == 0 && start == 0
@@ -462,7 +504,12 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
462504
source
463505
}
464506

465-
filters = arrayOf(emptyEditTextBackspaceDetector)
507+
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O || Build.VERSION.SDK_INT == Build.VERSION_CODES.O_MR1) {
508+
// dynamicLayoutCrashPreventer needs to be first in array as these are going to be chained when processed
509+
filters = arrayOf(dynamicLayoutCrashPreventer, emptyEditTextBackspaceDetector)
510+
} else {
511+
filters = arrayOf(emptyEditTextBackspaceDetector)
512+
}
466513
}
467514

468515
private fun handleBackspaceAndEnter(event: KeyEvent): Boolean {
@@ -1303,6 +1350,26 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
13031350
bypassObservationQueue = false
13041351
}
13051352

1353+
fun disableCrashPreventerInputFilter() {
1354+
bypassCrashPreventerInputFilter = true
1355+
}
1356+
1357+
fun enableCrashPreventerInputFilter() {
1358+
bypassCrashPreventerInputFilter = false
1359+
}
1360+
1361+
fun disableMediaDeletedListener() {
1362+
bypassMediaDeletedListener = true
1363+
}
1364+
1365+
fun enableMediaDeletedListener() {
1366+
bypassMediaDeletedListener = false
1367+
}
1368+
1369+
fun isMediaDeletedListenerDisabled(): Boolean {
1370+
return bypassMediaDeletedListener
1371+
}
1372+
13061373
fun isTextChangedListenerDisabled(): Boolean {
13071374
return consumeEditEvent
13081375
}

aztec/src/main/kotlin/org/wordpress/aztec/watchers/DeleteMediaElementWatcherAPI25AndHigher.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ class DeleteMediaElementWatcherAPI25AndHigher(aztecText: AztecText) : TextWatche
1717
return
1818
}
1919

20+
if (aztecTextRef.get()?.isMediaDeletedListenerDisabled() ?: true) {
21+
return
22+
}
23+
2024
if (count > 0) {
2125
deleted = true
2226

aztec/src/main/kotlin/org/wordpress/aztec/watchers/DeleteMediaElementWatcherPreAPI25.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ class DeleteMediaElementWatcherPreAPI25(aztecText: AztecText) : TextWatcher {
1414
return
1515
}
1616

17+
if (aztecTextRef.get()?.isMediaDeletedListenerDisabled() ?: true) {
18+
return
19+
}
20+
1721
if (count > 0) {
1822
aztecTextRef.get()?.text?.getSpans(start, start + count, AztecMediaSpan::class.java)
1923
?.forEach {

0 commit comments

Comments
 (0)