@@ -37,10 +37,46 @@ class InputConnectionAdaptor extends BaseInputConnection {
37
37
private int mBatchCount ;
38
38
private InputMethodManager mImm ;
39
39
private final Layout mLayout ;
40
-
41
40
// Used to determine if Samsung-specific hacks should be applied.
42
41
private final boolean isSamsung ;
43
42
43
+ private boolean mRepeatCheckNeeded = false ;
44
+ private TextEditingValue mLastSentTextEditngValue ;
45
+ // Data class used to get and store the last-sent values via updateEditingState to
46
+ // the framework. These are then compared against to prevent redundant messages
47
+ // with the same data before any valid operations were made to the contents.
48
+ private class TextEditingValue {
49
+ public int selectionStart ;
50
+ public int selectionEnd ;
51
+ public int composingStart ;
52
+ public int composingEnd ;
53
+ public String text ;
54
+
55
+ public TextEditingValue (Editable editable ) {
56
+ selectionStart = Selection .getSelectionStart (editable );
57
+ selectionEnd = Selection .getSelectionEnd (editable );
58
+ composingStart = BaseInputConnection .getComposingSpanStart (editable );
59
+ composingEnd = BaseInputConnection .getComposingSpanEnd (editable );
60
+ text = editable .toString ();
61
+ }
62
+
63
+ @ Override
64
+ public boolean equals (Object o ) {
65
+ if (o == this ) {
66
+ return true ;
67
+ }
68
+ if (!(o instanceof TextEditingValue )) {
69
+ return false ;
70
+ }
71
+ TextEditingValue value = (TextEditingValue ) o ;
72
+ return selectionStart == value .selectionStart
73
+ && selectionEnd == value .selectionEnd
74
+ && composingStart == value .composingStart
75
+ && composingEnd == value .composingEnd
76
+ && text .equals (value .text );
77
+ }
78
+ }
79
+
44
80
@ SuppressWarnings ("deprecation" )
45
81
public InputConnectionAdaptor (
46
82
View view ,
@@ -76,15 +112,42 @@ private void updateEditingState() {
76
112
// If the IME is in the middle of a batch edit, then wait until it completes.
77
113
if (mBatchCount > 0 ) return ;
78
114
79
- int selectionStart = Selection .getSelectionStart (mEditable );
80
- int selectionEnd = Selection .getSelectionEnd (mEditable );
81
- int composingStart = BaseInputConnection .getComposingSpanStart (mEditable );
82
- int composingEnd = BaseInputConnection .getComposingSpanEnd (mEditable );
115
+ TextEditingValue currentValue = new TextEditingValue (mEditable );
116
+
117
+ // Return if this data has already been sent and no meaningful changes have
118
+ // occurred to mark this as dirty. This prevents duplicate remote updates of
119
+ // the same data, which can break formatters that change the length of the
120
+ // contents.
121
+ if (mRepeatCheckNeeded && currentValue .equals (mLastSentTextEditngValue )) {
122
+ return ;
123
+ }
83
124
84
- mImm .updateSelection (mFlutterView , selectionStart , selectionEnd , composingStart , composingEnd );
125
+ mImm .updateSelection (
126
+ mFlutterView ,
127
+ currentValue .selectionStart ,
128
+ currentValue .selectionEnd ,
129
+ currentValue .composingStart ,
130
+ currentValue .composingEnd );
85
131
86
132
textInputChannel .updateEditingState (
87
- mClient , mEditable .toString (), selectionStart , selectionEnd , composingStart , composingEnd );
133
+ mClient ,
134
+ currentValue .text ,
135
+ currentValue .selectionStart ,
136
+ currentValue .selectionEnd ,
137
+ currentValue .composingStart ,
138
+ currentValue .composingEnd );
139
+
140
+ mRepeatCheckNeeded = true ;
141
+ mLastSentTextEditngValue = currentValue ;
142
+ }
143
+
144
+ // This should be called whenever a change could have been made to
145
+ // the value of mEditable, which will make any call of updateEditingState()
146
+ // ineligible for repeat checking as we do not want to skip sending real changes
147
+ // to the framework.
148
+ public void markDirty () {
149
+ // Disable updateEditngState's repeat-update check
150
+ mRepeatCheckNeeded = false ;
88
151
}
89
152
90
153
@ Override
@@ -109,7 +172,7 @@ public boolean endBatchEdit() {
109
172
@ Override
110
173
public boolean commitText (CharSequence text , int newCursorPosition ) {
111
174
boolean result = super .commitText (text , newCursorPosition );
112
- updateEditingState ();
175
+ markDirty ();
113
176
return result ;
114
177
}
115
178
@@ -118,14 +181,21 @@ public boolean deleteSurroundingText(int beforeLength, int afterLength) {
118
181
if (Selection .getSelectionStart (mEditable ) == -1 ) return true ;
119
182
120
183
boolean result = super .deleteSurroundingText (beforeLength , afterLength );
121
- updateEditingState ();
184
+ markDirty ();
185
+ return result ;
186
+ }
187
+
188
+ @ Override
189
+ public boolean deleteSurroundingTextInCodePoints (int beforeLength , int afterLength ) {
190
+ boolean result = super .deleteSurroundingTextInCodePoints (beforeLength , afterLength );
191
+ markDirty ();
122
192
return result ;
123
193
}
124
194
125
195
@ Override
126
196
public boolean setComposingRegion (int start , int end ) {
127
197
boolean result = super .setComposingRegion (start , end );
128
- updateEditingState ();
198
+ markDirty ();
129
199
return result ;
130
200
}
131
201
@@ -137,7 +207,7 @@ public boolean setComposingText(CharSequence text, int newCursorPosition) {
137
207
} else {
138
208
result = super .setComposingText (text , newCursorPosition );
139
209
}
140
- updateEditingState ();
210
+ markDirty ();
141
211
return result ;
142
212
}
143
213
@@ -159,7 +229,7 @@ public boolean finishComposingText() {
159
229
}
160
230
}
161
231
162
- updateEditingState ();
232
+ markDirty ();
163
233
return result ;
164
234
}
165
235
@@ -173,6 +243,13 @@ public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
173
243
return extractedText ;
174
244
}
175
245
246
+ @ Override
247
+ public boolean clearMetaKeyStates (int states ) {
248
+ boolean result = super .clearMetaKeyStates (states );
249
+ markDirty ();
250
+ return result ;
251
+ }
252
+
176
253
// Detect if the keyboard is a Samsung keyboard, where we apply Samsung-specific hacks to
177
254
// fix critical bugs that make the keyboard otherwise unusable. See finishComposingText() for
178
255
// more details.
@@ -197,7 +274,7 @@ private boolean isSamsung() {
197
274
@ Override
198
275
public boolean setSelection (int start , int end ) {
199
276
boolean result = super .setSelection (start , end );
200
- updateEditingState ();
277
+ markDirty ();
201
278
return result ;
202
279
}
203
280
@@ -219,6 +296,7 @@ private static int clampIndexToEditable(int index, Editable editable) {
219
296
220
297
@ Override
221
298
public boolean sendKeyEvent (KeyEvent event ) {
299
+ markDirty ();
222
300
if (event .getAction () == KeyEvent .ACTION_DOWN ) {
223
301
if (event .getKeyCode () == KeyEvent .KEYCODE_DEL ) {
224
302
int selStart = clampIndexToEditable (Selection .getSelectionStart (mEditable ), mEditable );
@@ -344,6 +422,7 @@ public boolean sendKeyEvent(KeyEvent event) {
344
422
345
423
@ Override
346
424
public boolean performContextMenuAction (int id ) {
425
+ markDirty ();
347
426
if (id == android .R .id .selectAll ) {
348
427
setSelection (0 , mEditable .length ());
349
428
return true ;
@@ -397,6 +476,7 @@ public boolean performContextMenuAction(int id) {
397
476
398
477
@ Override
399
478
public boolean performEditorAction (int actionCode ) {
479
+ markDirty ();
400
480
switch (actionCode ) {
401
481
case EditorInfo .IME_ACTION_NONE :
402
482
textInputChannel .newline (mClient );
0 commit comments