@@ -37,10 +37,46 @@ class InputConnectionAdaptor extends BaseInputConnection {
3737 private int mBatchCount ;
3838 private InputMethodManager mImm ;
3939 private final Layout mLayout ;
40-
4140 // Used to determine if Samsung-specific hacks should be applied.
4241 private final boolean isSamsung ;
4342
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+
4480 @ SuppressWarnings ("deprecation" )
4581 public InputConnectionAdaptor (
4682 View view ,
@@ -76,15 +112,42 @@ private void updateEditingState() {
76112 // If the IME is in the middle of a batch edit, then wait until it completes.
77113 if (mBatchCount > 0 ) return ;
78114
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+ }
83124
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 );
85131
86132 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 ;
88151 }
89152
90153 @ Override
@@ -109,7 +172,7 @@ public boolean endBatchEdit() {
109172 @ Override
110173 public boolean commitText (CharSequence text , int newCursorPosition ) {
111174 boolean result = super .commitText (text , newCursorPosition );
112- updateEditingState ();
175+ markDirty ();
113176 return result ;
114177 }
115178
@@ -118,14 +181,21 @@ public boolean deleteSurroundingText(int beforeLength, int afterLength) {
118181 if (Selection .getSelectionStart (mEditable ) == -1 ) return true ;
119182
120183 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 ();
122192 return result ;
123193 }
124194
125195 @ Override
126196 public boolean setComposingRegion (int start , int end ) {
127197 boolean result = super .setComposingRegion (start , end );
128- updateEditingState ();
198+ markDirty ();
129199 return result ;
130200 }
131201
@@ -137,7 +207,7 @@ public boolean setComposingText(CharSequence text, int newCursorPosition) {
137207 } else {
138208 result = super .setComposingText (text , newCursorPosition );
139209 }
140- updateEditingState ();
210+ markDirty ();
141211 return result ;
142212 }
143213
@@ -159,7 +229,7 @@ public boolean finishComposingText() {
159229 }
160230 }
161231
162- updateEditingState ();
232+ markDirty ();
163233 return result ;
164234 }
165235
@@ -173,6 +243,13 @@ public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
173243 return extractedText ;
174244 }
175245
246+ @ Override
247+ public boolean clearMetaKeyStates (int states ) {
248+ boolean result = super .clearMetaKeyStates (states );
249+ markDirty ();
250+ return result ;
251+ }
252+
176253 // Detect if the keyboard is a Samsung keyboard, where we apply Samsung-specific hacks to
177254 // fix critical bugs that make the keyboard otherwise unusable. See finishComposingText() for
178255 // more details.
@@ -197,7 +274,7 @@ private boolean isSamsung() {
197274 @ Override
198275 public boolean setSelection (int start , int end ) {
199276 boolean result = super .setSelection (start , end );
200- updateEditingState ();
277+ markDirty ();
201278 return result ;
202279 }
203280
@@ -219,6 +296,7 @@ private static int clampIndexToEditable(int index, Editable editable) {
219296
220297 @ Override
221298 public boolean sendKeyEvent (KeyEvent event ) {
299+ markDirty ();
222300 if (event .getAction () == KeyEvent .ACTION_DOWN ) {
223301 if (event .getKeyCode () == KeyEvent .KEYCODE_DEL ) {
224302 int selStart = clampIndexToEditable (Selection .getSelectionStart (mEditable ), mEditable );
@@ -344,6 +422,7 @@ public boolean sendKeyEvent(KeyEvent event) {
344422
345423 @ Override
346424 public boolean performContextMenuAction (int id ) {
425+ markDirty ();
347426 if (id == android .R .id .selectAll ) {
348427 setSelection (0 , mEditable .length ());
349428 return true ;
@@ -397,6 +476,7 @@ public boolean performContextMenuAction(int id) {
397476
398477 @ Override
399478 public boolean performEditorAction (int actionCode ) {
479+ markDirty ();
400480 switch (actionCode ) {
401481 case EditorInfo .IME_ACTION_NONE :
402482 textInputChannel .newline (mClient );
0 commit comments