Skip to content

Commit 6ec86aa

Browse files
authored
Merge pull request #595 from wordpress-mobile/issue/555-buffered-action-detection-part-2
Issue/555 buffered action detection part 2
2 parents 0984aa0 + 146e06b commit 6ec86aa

19 files changed

+276
-369
lines changed

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -814,7 +814,7 @@ class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknownHtmlT
814814
// we need to make a copy to preserve the contents as they were before the change
815815
val textCopy = SpannableStringBuilder(text)
816816
val data = BeforeTextChangedEventData(textCopy, start, count, after)
817-
textWatcherEventBuilder.setBeforeTextChangedEvent(data)
817+
textWatcherEventBuilder.beforeEventData = data
818818
}
819819
}
820820

@@ -824,7 +824,7 @@ class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknownHtmlT
824824
if (!bypassObservationQueue) {
825825
val textCopy = SpannableStringBuilder(text)
826826
val data = OnTextChangedEventData(textCopy, start, before, count)
827-
textWatcherEventBuilder.setOnTextChangedEvent(data)
827+
textWatcherEventBuilder.onEventData = data
828828
}
829829
}
830830

@@ -836,7 +836,7 @@ class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknownHtmlT
836836
if (!bypassObservationQueue) {
837837
val textCopy = Editable.Factory.getInstance().newEditable(editableText)
838838
val data = AfterTextChangedEventData(textCopy)
839-
textWatcherEventBuilder.setAfterTextChangedEvent(data)
839+
textWatcherEventBuilder.afterEventData = data
840840

841841
// now that we have a full event cycle (before, on, and after) we can add the event to the observation queue
842842
observationQueue.add(textWatcherEventBuilder.build())

aztec/src/main/kotlin/org/wordpress/aztec/watchers/event/IEventInjector.java

Lines changed: 0 additions & 7 deletions
This file was deleted.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package org.wordpress.aztec.watchers.event
2+
3+
import org.wordpress.aztec.watchers.event.text.TextWatcherEvent
4+
5+
interface IEventInjector {
6+
fun executeEvent(data: TextWatcherEvent)
7+
}

aztec/src/main/kotlin/org/wordpress/aztec/watchers/event/buckets/API26Bucket.java renamed to aztec/src/main/kotlin/org/wordpress/aztec/watchers/event/buckets/API26Bucket.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
package org.wordpress.aztec.watchers.event.buckets;
1+
package org.wordpress.aztec.watchers.event.buckets
22

3-
import org.wordpress.aztec.watchers.event.sequence.known.space.API26InWordSpaceInsertionEvent;
3+
import org.wordpress.aztec.watchers.event.sequence.known.space.API26InWordSpaceInsertionEvent
44

5-
public class API26Bucket extends Bucket{
6-
7-
public API26Bucket() {
5+
class API26Bucket : Bucket() {
6+
init {
87
// constructor - here add all identified sequences for this bucket
9-
mUserOperations.add(new API26InWordSpaceInsertionEvent());
8+
userOperations.add(API26InWordSpaceInsertionEvent())
109
//mUserOperations.add(new ...);
1110
//mUserOperations.add(new ...);
1211
//mUserOperations.add(new ...);

aztec/src/main/kotlin/org/wordpress/aztec/watchers/event/buckets/Bucket.java

Lines changed: 0 additions & 15 deletions
This file was deleted.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.wordpress.aztec.watchers.event.buckets
2+
3+
import org.wordpress.aztec.watchers.event.sequence.UserOperationEvent
4+
5+
import java.util.ArrayList
6+
7+
/*
8+
extend from this class to construct a specific bucket
9+
*/
10+
abstract class Bucket {
11+
val userOperations = ArrayList<UserOperationEvent>()
12+
}

aztec/src/main/kotlin/org/wordpress/aztec/watchers/event/sequence/ObservationQueue.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@ import org.wordpress.aztec.watchers.event.buckets.API26Bucket
66
import org.wordpress.aztec.watchers.event.text.TextWatcherEvent
77
import org.wordpress.aztec.watchers.event.buckets.Bucket
88

9-
class ObservationQueue(injector: IEventInjector) : EventSequence<TextWatcherEvent>() {
9+
class ObservationQueue(val injector: IEventInjector) : EventSequence<TextWatcherEvent>() {
1010
val buckets = ArrayList<Bucket>()
11-
val injector = injector
1211

1312
init {
1413
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

aztec/src/main/kotlin/org/wordpress/aztec/watchers/event/sequence/known/space/API26InWordSpaceInsertionEvent.java

Lines changed: 0 additions & 140 deletions
This file was deleted.
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package org.wordpress.aztec.watchers.event.sequence.known.space
2+
3+
import org.apache.commons.lang3.StringUtils
4+
import org.wordpress.aztec.watchers.event.sequence.EventSequence
5+
import org.wordpress.aztec.watchers.event.sequence.UserOperationEvent
6+
import org.wordpress.aztec.watchers.event.sequence.known.space.steps.TextWatcherEventDeleteText
7+
import org.wordpress.aztec.watchers.event.sequence.known.space.steps.TextWatcherEventInsertText
8+
import org.wordpress.aztec.watchers.event.text.AfterTextChangedEventData
9+
import org.wordpress.aztec.watchers.event.text.TextWatcherEvent
10+
11+
/*
12+
This case implements the behavior observed in https://github.com/wordpress-mobile/AztecEditor-Android/issues/555
13+
*/
14+
class API26InWordSpaceInsertionEvent : UserOperationEvent() {
15+
private val SPACE = ' '
16+
private val SPACE_STRING = "" + SPACE
17+
private val MAXIMUM_TIME_BETWEEN_EVENTS_IN_PATTERN_MS = 50
18+
19+
init {
20+
// here we populate our model of reference (which is the sequence of events we expect to find)
21+
// note we don' populate the TextWatcherEvents with actual data, but rather we just want
22+
// to instantiate them so we can populate them later and test whether data holds true to their
23+
// validation.
24+
25+
// 2 generic deletes, followed by 2 generic inserts
26+
val builder = TextWatcherEventDeleteText.Builder()
27+
val step1 = builder.build()
28+
29+
val builderStep2 = TextWatcherEventDeleteText.Builder()
30+
val step2 = builderStep2.build()
31+
32+
val builderStep3 = TextWatcherEventInsertText.Builder()
33+
val step3 = builderStep3.build()
34+
35+
val builderStep4 = TextWatcherEventInsertText.Builder()
36+
val step4 = builderStep4.build()
37+
38+
// add each of the steps that make up for the identified API26InWordSpaceInsertionEvent here
39+
clear()
40+
addSequenceStep(step1)
41+
addSequenceStep(step2)
42+
addSequenceStep(step3)
43+
addSequenceStep(step4)
44+
}
45+
46+
override fun isUserOperationObservedInSequence(sequence: EventSequence<TextWatcherEvent>): Boolean {
47+
/* here check:
48+
49+
If we have 2 deletes followed by 2 inserts AND:
50+
1) checking the first BEFORETEXTCHANGED and
51+
2) checking the LAST AFTERTEXTCHANGED
52+
text length is longer by 1, and the item that is now located at the first BEFORETEXTCHANGED is a SPACE character.
53+
54+
*/
55+
if (this.sequence.size == sequence.size) {
56+
57+
// populate data in our own sequence to be able to run the comparator checks
58+
if (!isUserOperationPartiallyObservedInSequence(sequence)) {
59+
return false
60+
}
61+
62+
// ok all events are good individually and match the sequence we want to compare against.
63+
// now let's make sure the BEFORE / AFTER situation is what we are trying to identify
64+
val firstEvent = sequence.first()
65+
val lastEvent = sequence[sequence.size - 1]
66+
67+
// if new text length is longer than original text by 1
68+
if (firstEvent.beforeEventData.textBefore?.length == lastEvent.afterEventData.textAfter!!.length - 1) {
69+
// now check that the inserted character is actually a space
70+
//val (_, start, count) = firstEvent.beforeEventData
71+
val data = firstEvent.beforeEventData
72+
if (lastEvent.afterEventData.textAfter!![data.start + data.count] == SPACE) {
73+
return true
74+
}
75+
}
76+
}
77+
78+
return false
79+
}
80+
81+
override fun isUserOperationPartiallyObservedInSequence(sequence: EventSequence<TextWatcherEvent>): Boolean {
82+
for (i in sequence.indices) {
83+
84+
val eventHolder = this.sequence[i]
85+
val observableEvent = sequence[i]
86+
87+
// if time distance between any of the events is longer than 50 millis, discard this as this pattern is
88+
// likely not produced by the platform, but rather the user.
89+
// WARNING! When debugging with breakpoints, you should disable this check as time can exceed the 50 MS limit and
90+
// create undesired behavior.
91+
if (i > 0) { // only try to compare when we have at least 2 events, so we can compare with the previous one
92+
val timestampForPreviousEvent = sequence[i - 1].timestamp
93+
val timeDistance = observableEvent.timestamp - timestampForPreviousEvent
94+
if (timeDistance > MAXIMUM_TIME_BETWEEN_EVENTS_IN_PATTERN_MS) {
95+
return false
96+
}
97+
}
98+
99+
eventHolder.beforeEventData = observableEvent.beforeEventData
100+
eventHolder.onEventData = observableEvent.onEventData
101+
eventHolder.afterEventData = observableEvent.afterEventData
102+
103+
// return immediately as soon as we realize the pattern diverges
104+
if (!eventHolder.testFitsBeforeOnAndAfter()) {
105+
return false
106+
}
107+
}
108+
109+
return true
110+
}
111+
112+
override fun buildReplacementEventWithSequenceData(sequence: EventSequence<TextWatcherEvent>): TextWatcherEvent {
113+
val builder = TextWatcherEventInsertText.Builder()
114+
// here make it all up as a unique event that does the insert as usual, as we'd get it on older APIs
115+
val firstEvent = sequence.first()
116+
val lastEvent = sequence[sequence.size - 1]
117+
118+
val (oldText) = firstEvent.beforeEventData
119+
120+
val differenceIndex = StringUtils.indexOfDifference(oldText, lastEvent.afterEventData.textAfter)
121+
oldText?.insert(differenceIndex, SPACE_STRING)
122+
123+
builder.afterEventData = AfterTextChangedEventData(oldText)
124+
val replacementEvent = builder.build()
125+
replacementEvent.insertionStart = differenceIndex
126+
replacementEvent.insertionLength = 1
127+
128+
return replacementEvent
129+
}
130+
}

0 commit comments

Comments
 (0)