Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
6684c9e
Add new channel updateEditingStateWithDelta
Renzo-Olivares Aug 18, 2021
a15925b
Fix formatting
Renzo-Olivares Aug 18, 2021
a56f86b
Fix formatting
Renzo-Olivares Aug 18, 2021
bf0ae8e
Fix formatting
Renzo-Olivares Aug 18, 2021
f2cc57b
Add equality case
Renzo-Olivares Aug 18, 2021
6c5d5cf
formatting
Renzo-Olivares Aug 18, 2021
9e5a8c1
Implement TextEditingDelta class to encapsulate delta logic
Renzo-Olivares Aug 19, 2021
b15a970
Formatting
Renzo-Olivares Aug 19, 2021
f3be317
Also send new composing/selection along with delta
Renzo-Olivares Aug 20, 2021
65f0f71
Utilize an ArrayList of TextEditingDeltas to take into account batch …
Renzo-Olivares Aug 20, 2021
c87b2b4
clear batch deltas when editing state is updated by framework, we don…
Renzo-Olivares Aug 21, 2021
34ed4cd
fix formatting
Renzo-Olivares Aug 21, 2021
e9742f8
formatting
Renzo-Olivares Aug 21, 2021
e0278c9
formatting
Renzo-Olivares Aug 21, 2021
0bca5e9
Add enableDeltaModel flag to choose between updateEditingValue and up…
Renzo-Olivares Aug 22, 2021
fd84d6a
Add autofill support for delta model
Renzo-Olivares Aug 22, 2021
a608ee3
formatting
Renzo-Olivares Aug 22, 2021
e182d61
update
Renzo-Olivares Aug 22, 2021
d2097ad
Fix issues with composing region
Renzo-Olivares Aug 24, 2021
6f96926
formatting
Renzo-Olivares Aug 24, 2021
89c4773
Remove
Renzo-Olivares Aug 24, 2021
d436a4f
Fix out of bounds error when textfield is initialized with text
Renzo-Olivares Aug 25, 2021
8c4ef06
Clean up logs
Renzo-Olivares Aug 25, 2021
92e30c9
formatting
Renzo-Olivares Aug 25, 2021
f7858a1
update licenses
Renzo-Olivares Aug 26, 2021
6192ca4
Fix deletion issues
Renzo-Olivares Aug 27, 2021
7791598
formatting
Renzo-Olivares Aug 27, 2021
5ff5114
delta -> deltaType
Renzo-Olivares Aug 27, 2021
71086dc
updates
Renzo-Olivares Aug 29, 2021
b717cb7
formatting
Renzo-Olivares Aug 29, 2021
3d24083
formatting
Renzo-Olivares Aug 29, 2021
8e78152
Fix logs
Renzo-Olivares Aug 29, 2021
e6bb58a
Add tests for TextEditingDelta
Renzo-Olivares Aug 30, 2021
327403d
formatting fixes
Renzo-Olivares Aug 30, 2021
31c188d
remove unused imports
Renzo-Olivares Aug 30, 2021
c4bb736
Fix return type for getDeltaType
Renzo-Olivares Aug 30, 2021
8e8e29e
Fix tests
Renzo-Olivares Aug 30, 2021
f322d13
Fix ListenableEditingStateTest
Renzo-Olivares Aug 30, 2021
0c7c3a9
formatting
Renzo-Olivares Aug 30, 2021
a5cc483
Fix typo
Renzo-Olivares Aug 30, 2021
8a432f2
Add test for deltas when setComposingText
Renzo-Olivares Aug 30, 2021
174bc56
formatting
Renzo-Olivares Aug 30, 2021
1f9b195
Updates to test
Renzo-Olivares Aug 30, 2021
4a3f1e2
fix typo
Renzo-Olivares Aug 30, 2021
794f288
formatting
Renzo-Olivares Aug 30, 2021
2cd3260
Verify all properties of delta for test
Renzo-Olivares Aug 30, 2021
b5bcd1b
formatting
Renzo-Olivares Aug 30, 2021
7c5c287
Add VisibleForTesting import
Renzo-Olivares Aug 30, 2021
5d3468f
fix test
Renzo-Olivares Aug 30, 2021
e4430c2
fix formatting
Renzo-Olivares Aug 30, 2021
11d6ddd
fix tests
Renzo-Olivares Aug 30, 2021
eb94a62
fix tests
Renzo-Olivares Aug 30, 2021
a204519
fix tests
Renzo-Olivares Aug 30, 2021
e6b86eb
updates for tests
Renzo-Olivares Aug 30, 2021
1580b3d
remove ide made file
Renzo-Olivares Aug 30, 2021
2d1655a
Fix more tests
Renzo-Olivares Aug 30, 2021
d7a8800
Fix TextEditingDelta tests
Renzo-Olivares Aug 30, 2021
2c60053
Add test for verifying delta is generated on setComposingText
Renzo-Olivares Aug 30, 2021
b7ac52c
Add TextInputPlugin tests for replacement and deletion deltas
Renzo-Olivares Aug 30, 2021
c66555d
formatting
Renzo-Olivares Aug 30, 2021
73c849e
formatting
Renzo-Olivares Aug 30, 2021
b54b8b1
Capture delta on setSpan and clear deltas when generated from the fra…
Renzo-Olivares Sep 1, 2021
9496733
fix formatting
Renzo-Olivares Sep 1, 2021
07f0c1d
update tests to reflect new clear batch delta calls
Renzo-Olivares Sep 1, 2021
2326d7b
Make deletion inline with platform
Renzo-Olivares Sep 1, 2021
510ca19
fix tests
Renzo-Olivares Sep 1, 2021
14c5f12
Address reviewer comments
Renzo-Olivares Sep 7, 2021
12da161
formatting
Renzo-Olivares Sep 7, 2021
8d34321
fix tests
Renzo-Olivares Sep 7, 2021
f858e9f
fix tests
Renzo-Olivares Sep 7, 2021
e493f6e
move delta inference to framework
Renzo-Olivares Sep 7, 2021
7d2c858
update tests
Renzo-Olivares Sep 7, 2021
d0659f0
formatting
Renzo-Olivares Sep 7, 2021
7bacbed
fix tests
Renzo-Olivares Sep 7, 2021
88f1042
Formatting
Renzo-Olivares Sep 7, 2021
034f0e8
get -> extract
Renzo-Olivares Sep 7, 2021
630b505
More tests and address reviewer comments
Renzo-Olivares Sep 8, 2021
81232af
clean up logs
Renzo-Olivares Sep 8, 2021
4b25c8e
Address reviewer comments
Renzo-Olivares Sep 9, 2021
1cd7aa1
formatting
Renzo-Olivares Sep 9, 2021
9d7b337
formatting
Renzo-Olivares Sep 9, 2021
72ab127
Remove testing constructor
Renzo-Olivares Sep 14, 2021
4a13b0a
send as deltas
Renzo-Olivares Sep 15, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -900,6 +900,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/FlutterT
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/ImeSyncDeferringInsetsCallback.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/ListenableEditingState.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/TextEditingDelta.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/localization/LocalizationPlugin.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/mouse/MouseCursorPlugin.java
Expand Down
1 change: 1 addition & 0 deletions shell/platform/android/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ android_java_sources = [
"io/flutter/plugin/editing/ImeSyncDeferringInsetsCallback.java",
"io/flutter/plugin/editing/InputConnectionAdaptor.java",
"io/flutter/plugin/editing/ListenableEditingState.java",
"io/flutter/plugin/editing/TextEditingDelta.java",
"io/flutter/plugin/editing/TextInputPlugin.java",
"io/flutter/plugin/localization/LocalizationPlugin.java",
"io/flutter/plugin/mouse/MouseCursorPlugin.java",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import io.flutter.plugin.common.JSONMethodCodec;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.editing.TextEditingDelta;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
Expand Down Expand Up @@ -186,6 +188,18 @@ private static HashMap<Object, Object> createEditingStateJSON(
state.put("composingExtent", composingEnd);
return state;
}

private static HashMap<Object, Object> createEditingDeltaJSON(
ArrayList<TextEditingDelta> batchDeltas) {
HashMap<Object, Object> state = new HashMap<>();

JSONArray deltas = new JSONArray();
for (TextEditingDelta delta : batchDeltas) {
deltas.put(delta.toJSON());
}
state.put("deltas", deltas);
return state;
}
/**
* Instructs Flutter to update its text input editing state to reflect the given configuration.
*/
Expand Down Expand Up @@ -220,6 +234,21 @@ public void updateEditingState(
channel.invokeMethod("TextInputClient.updateEditingState", Arrays.asList(inputClientId, state));
}

public void updateEditingStateWithDeltas(
int inputClientId, ArrayList<TextEditingDelta> batchDeltas) {

Log.v(
TAG,
"Sending message to update editing state with deltas: \n"
+ "Number of deltas: "
+ batchDeltas.size());

final HashMap<Object, Object> state = createEditingDeltaJSON(batchDeltas);

channel.invokeMethod(
"TextInputClient.updateEditingStateWithDeltas", Arrays.asList(inputClientId, state));
}

public void updateEditingStateWithTag(
int inputClientId, HashMap<String, TextEditState> editStates) {
Log.v(
Expand Down Expand Up @@ -428,6 +457,7 @@ public static Configuration fromJson(@NonNull JSONObject json)
json.optBoolean("autocorrect", true),
json.optBoolean("enableSuggestions"),
json.optBoolean("enableIMEPersonalizedLearning"),
json.optBoolean("enableDeltaModel"),
TextCapitalization.fromValue(json.getString("textCapitalization")),
InputType.fromJson(json.getJSONObject("inputType")),
inputAction,
Expand Down Expand Up @@ -583,6 +613,7 @@ public Autofill(
public final boolean autocorrect;
public final boolean enableSuggestions;
public final boolean enableIMEPersonalizedLearning;
public final boolean enableDeltaModel;
@NonNull public final TextCapitalization textCapitalization;
@NonNull public final InputType inputType;
@Nullable public final Integer inputAction;
Expand All @@ -595,6 +626,7 @@ public Configuration(
boolean autocorrect,
boolean enableSuggestions,
boolean enableIMEPersonalizedLearning,
boolean enableDeltaModel,
@NonNull TextCapitalization textCapitalization,
@NonNull InputType inputType,
@Nullable Integer inputAction,
Expand All @@ -605,6 +637,7 @@ public Configuration(
this.autocorrect = autocorrect;
this.enableSuggestions = enableSuggestions;
this.enableIMEPersonalizedLearning = enableIMEPersonalizedLearning;
this.enableDeltaModel = enableDeltaModel;
this.textCapitalization = textCapitalization;
this.inputType = inputType;
this.inputAction = inputAction;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ void didChangeEditingState(
private int mChangeNotificationDepth = 0;
private ArrayList<EditingStateWatcher> mListeners = new ArrayList<>();
private ArrayList<EditingStateWatcher> mPendingListeners = new ArrayList<>();
private ArrayList<TextEditingDelta> mBatchTextEditingDeltas = new ArrayList<>();

private String mToStringCache;

Expand Down Expand Up @@ -70,6 +71,17 @@ public Editable getEditable() {
};
}

public ArrayList<TextEditingDelta> extractBatchTextEditingDeltas() {
ArrayList<TextEditingDelta> currentBatchDeltas =
new ArrayList<TextEditingDelta>(mBatchTextEditingDeltas);
mBatchTextEditingDeltas.clear();
return currentBatchDeltas;
}

public void clearBatchDeltas() {
mBatchTextEditingDeltas.clear();
}

/// Starts a new batch edit during which change notifications will be put on hold until all batch
/// edits end.
///
Expand Down Expand Up @@ -142,7 +154,13 @@ public void setEditingState(TextInputChannel.TextEditState newState) {
} else {
Selection.removeSelection(this);
}

setComposingRange(newState.composingStart, newState.composingEnd);

// Updates from the framework should not have a delta created for it as they have already been
// applied on the framework side.
clearBatchDeltas();

endBatchEdit();
}

Expand Down Expand Up @@ -179,6 +197,8 @@ public SpannableStringBuilder replace(
Log.e(TAG, "editing state should not be changed in a listener callback");
}

final CharSequence oldText = toString();

boolean textChanged = end - start != tbend - tbstart;
for (int i = 0; i < end - start && !textChanged; i++) {
textChanged |= charAt(start + i) != tb.charAt(tbstart + i);
Expand All @@ -193,6 +213,17 @@ public SpannableStringBuilder replace(
final int composingEnd = getComposingEnd();

final SpannableStringBuilder editable = super.replace(start, end, tb, tbstart, tbend);
mBatchTextEditingDeltas.add(
new TextEditingDelta(
oldText,
start,
end,
tb,
getSelectionStart(),
getSelectionEnd(),
getComposingStart(),
getComposingEnd()));

if (mBatchEditNestDepth > 0) {
return editable;
}
Expand Down Expand Up @@ -240,6 +271,20 @@ public final int getComposingEnd() {
return BaseInputConnection.getComposingSpanEnd(this);
}

@Override
public void setSpan(Object what, int start, int end, int flags) {
super.setSpan(what, start, end, flags);
// Setting a span does not involve mutating the text value in the editing state. Here we create
// a non text update delta with any updated selection and composing regions.
mBatchTextEditingDeltas.add(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible for setSpan to be invoked from within the replace method? If it is then when that happens we'll see an extra nonTextUpdate for the change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is possible but it shouldn't affect the final result since they are nonTextUpdates. In the end the final TextEditingDelta in the batch will have the most up to date selection/composing regions. In theory if setSpan is called in replace we will want to capture that anyways.

Copy link
Contributor Author

@Renzo-Olivares Renzo-Olivares Sep 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The batch may look something like this if super.replace() calls setSpan.
[nonTextUpdate, nonTextUpdate, nonTextUpdate, delta]
In this case the last delta will still have the most up to date composing/selection regions. We capture the delta after super.replace() has been called so the actual delta will always be the last one applied.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally there should be no overlap between consecutive deltas and there should be no redundant deltas. Should be fine for now.

new TextEditingDelta(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should make a "nonTextUpdate" delta be its own constructor. It is not always obvious to pass "", -1, -1. A separate constructor for a separate case would prevent any accidental misuse or misconfiguration of this particular type of delta.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes sense, I do this already in the iOS PR.

toString(),
getSelectionStart(),
getSelectionEnd(),
getComposingStart(),
getComposingEnd()));
}

@Override
public String toString() {
return mToStringCache != null ? mToStringCache : (mToStringCache = super.toString());
Expand Down
127 changes: 127 additions & 0 deletions shell/platform/android/io/flutter/plugin/editing/TextEditingDelta.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugin.editing;

import androidx.annotation.VisibleForTesting;
import io.flutter.Log;
import org.json.JSONException;
import org.json.JSONObject;

/// A representation of the change that occured to an editing state, along with the resulting
/// composing and selection regions.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we explicitly differentiate between text content changes and selection/composing region index only changes? However it is handled, it should be documented.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never mind, that is under the umbrella of TextEditingDeltaNonTextUpdate

Copy link
Contributor Author

@Renzo-Olivares Renzo-Olivares Sep 14, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the framework we do, but on the engine there is no differentiation. This differentiation is documented on the framework side, should it also have something here?

Edit: didn't see your comment before this, disregard.

public final class TextEditingDelta {
private CharSequence oldText;
private CharSequence deltaText;
private int deltaStart;
private int deltaEnd;
private int newSelectionStart;
private int newSelectionEnd;
private int newComposingStart;
private int newComposingEnd;

private static final String TAG = "TextEditingDelta";

public TextEditingDelta(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was expecting that we can just extract the tb{start, end}, start, end and tb from the replace implementation and send them verbatim to the framework, and the framework will have to implement the same replace method to apply it to a TextEditingValue. Would that not work?

Copy link
Contributor Author

@Renzo-Olivares Renzo-Olivares Aug 31, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That wouldn't work in some cases. Say the case where we are typing "world".

Current state: "worl|"
soft keyboard input: "d"
replace method call: replace(0,4, world, 0, 5)

or for a deletion

Current state: "world|"
soft keyboard input: backspace
replace method call: replace(0,5, worl, 0, 4)

If typing at the end of the composing region the text in the composing region is replaced with the entire new composing text.

If we move the cursor inside the word currently being composed like below we get this call.
current state: "wo|ld"
soft keyboard input: "r"
replace method call: replace(2,2, r, 0,1)

Here we are actually given the insertion point and the character inserted, where when it is at the end of the composing region we must do some reasoning.

On iOS we have something slightly different as well.

Current state: "world|"
soft keyboard input: backspace
replaceRangeWithLocal (4,5) with empty string ranges (0,0)

another thing iOS does is when typing with a CJK keyboard it will exhibit behavior similar to Android composing region where the entire composing region is replaced on input.

Because these values can vary a bit by platform for the same operation I think it is important to unify them into one format. I'm unsure if a replacement of "world" with "worl" would have the same result as replacing "d" in "world" with an empty string regarding if ranges of "world" where bolded/styled. Would these ranges still have the information needed to contract/expand as expected?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah makes sense, thank you for the clarification! Do you think we'll be able to extract that info directly from the InputConnection interface (instead of replace)? It doesn't have to be implemented that way right now if it is (it's probably going to be a little complicated to implement since InputConnectionAdaptor and TextInputPlugin currently don't directly talk to each other). I'm just wondering if we can avoid the manual diffing process here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately from my research of the API I don't think so. Even the InputConnectionAdaptor does not provide these granular differences between editing states.

InputConnectionAdaptor.commitText and InputConnectionAdaptor.setComposingText both call their super class BaseInputConnection which calls replaceText() and routes that to SpannableStringBuilder.replace(). Only in some cases will the IME actually provide granular changes like in InputConnectionAdaptor.deleteSurroundingText which does call SpannableStringBuilder.delete() that is in the end also routed to replace(). In most cases for Android since the composing region is always active we are always replacing the entire composing region.

Maybe I missed something but this is my conclusion after my research.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. So setComposingText and commit* can delete/insert/replace, but they all only operate on a specific range.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer a bit more documentation in this class. If this were the entrypoint for someone learning about this feature, the documentation here is a bit lacking to properly introduce what this does and find the rest of the code.

CharSequence oldEditable,
int replacementDestinationStart,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since in this system, we have a Delta type called replacement, we should avoid using the name replacement to describe two separate things. Can we not just use delta<blah> like you do in the constructor for testing below?

Also, what is the difference between that testing constructor and this one? They seem to do the same thing, but parameter naming is out of sync.

Copy link
Contributor Author

@Renzo-Olivares Renzo-Olivares Sep 14, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I don't call it delta<blah> is because the parameters are coming straight from Android replace method. The actual deltas are set by setDeltas which does have delta<blah> as the parameters.

Your right the testing constructor can be eliminated.

int replacementDestinationEnd,
CharSequence replacementSource,
int selectionStart,
int selectionEnd,
int composingStart,
int composingEnd) {
newSelectionStart = selectionStart;
newSelectionEnd = selectionEnd;
newComposingStart = composingStart;
newComposingEnd = composingEnd;

setDeltas(
oldEditable,
replacementSource.toString(),
replacementDestinationStart,
replacementDestinationEnd);
}

// Non text update delta constructor.
public TextEditingDelta(
CharSequence oldText,
int selectionStart,
int selectionEnd,
int composingStart,
int composingEnd) {
newSelectionStart = selectionStart;
newSelectionEnd = selectionEnd;
newComposingStart = composingStart;
newComposingEnd = composingEnd;

setDeltas(oldText, "", -1, -1);
}

@VisibleForTesting
public CharSequence getOldText() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need these getters? The text input plugin only need to be able to build deltas and serialize them to JSON right? Are these for writing tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes there are now purely for tests.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Maybe write a comment or comments stating that these are for testing only.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know java too well but do you need expose them as public if the test is in the same package?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think we do. I just tried to switch them to private and it complained.

return oldText;
}

@VisibleForTesting
public CharSequence getDeltaText() {
return deltaText;
}

@VisibleForTesting
public int getDeltaStart() {
return deltaStart;
}

@VisibleForTesting
public int getDeltaEnd() {
return deltaEnd;
}

@VisibleForTesting
public int getNewSelectionStart() {
return newSelectionStart;
}

@VisibleForTesting
public int getNewSelectionEnd() {
return newSelectionEnd;
}

@VisibleForTesting
public int getNewComposingStart() {
return newComposingStart;
}

@VisibleForTesting
public int getNewComposingEnd() {
return newComposingEnd;
}

private void setDeltas(CharSequence oldText, CharSequence newText, int newStart, int newExtent) {
this.oldText = oldText;
deltaText = newText;
deltaStart = newStart;
deltaEnd = newExtent;
}

public JSONObject toJSON() {
JSONObject delta = new JSONObject();

try {
delta.put("oldText", oldText.toString());
delta.put("deltaText", deltaText.toString());
delta.put("deltaStart", deltaStart);
delta.put("deltaEnd", deltaEnd);
delta.put("selectionBase", newSelectionStart);
delta.put("selectionExtent", newSelectionEnd);
delta.put("composingBase", newComposingStart);
delta.put("composingExtent", newComposingEnd);
} catch (JSONException e) {
Log.e(TAG, "unable to create JSONObject: " + e);
}

return delta;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import io.flutter.embedding.engine.systemchannels.TextInputChannel;
import io.flutter.embedding.engine.systemchannels.TextInputChannel.TextEditState;
import io.flutter.plugin.platform.PlatformViewsController;
import java.util.ArrayList;
import java.util.HashMap;

/** Android implementation of the text input plugin. */
Expand Down Expand Up @@ -624,6 +625,9 @@ public void didChangeEditingState(
final int selectionEnd = mEditable.getSelectionEnd();
final int composingStart = mEditable.getComposingStart();
final int composingEnd = mEditable.getComposingEnd();

final ArrayList<TextEditingDelta> batchTextEditingDeltas =
mEditable.extractBatchTextEditingDeltas();
final boolean skipFrameworkUpdate =
// The framework needs to send its editing state first.
mLastKnownFrameworkTextEditingState == null
Expand All @@ -634,16 +638,25 @@ public void didChangeEditingState(
&& composingEnd == mLastKnownFrameworkTextEditingState.composingEnd);
if (!skipFrameworkUpdate) {
Log.v(TAG, "send EditingState to flutter: " + mEditable.toString());
textInputChannel.updateEditingState(
inputTarget.id,
mEditable.toString(),
selectionStart,
selectionEnd,
composingStart,
composingEnd);

if (configuration.enableDeltaModel) {
textInputChannel.updateEditingStateWithDeltas(inputTarget.id, batchTextEditingDeltas);
mEditable.clearBatchDeltas();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This clearBatchDeltas makes sense!

} else {
textInputChannel.updateEditingState(
inputTarget.id,
mEditable.toString(),
selectionStart,
selectionEnd,
composingStart,
composingEnd);
}
mLastKnownFrameworkTextEditingState =
new TextEditState(
mEditable.toString(), selectionStart, selectionEnd, composingStart, composingEnd);
} else {
// Don't accumulate deltas if they are not sent to the framework.
mEditable.clearBatchDeltas();
}
}

Expand Down Expand Up @@ -817,7 +830,6 @@ public void autofill(SparseArray<AutofillValue> values) {
editingValues.put(autofill.uniqueIdentifier, newState);
}
}

textInputChannel.updateEditingStateWithTag(inputTarget.id, editingValues);
}
// -------- End: Autofill -------
Expand Down
2 changes: 2 additions & 0 deletions shell/platform/android/test/io/flutter/FlutterTestSuite.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import io.flutter.plugin.common.StandardMethodCodecTest;
import io.flutter.plugin.editing.InputConnectionAdaptorTest;
import io.flutter.plugin.editing.ListenableEditingStateTest;
import io.flutter.plugin.editing.TextEditingDeltaTest;
import io.flutter.plugin.editing.TextInputPluginTest;
import io.flutter.plugin.localization.LocalizationPluginTest;
import io.flutter.plugin.mouse.MouseCursorPluginTest;
Expand Down Expand Up @@ -99,6 +100,7 @@
SmokeTest.class,
StandardMessageCodecTest.class,
StandardMethodCodecTest.class,
TextEditingDeltaTest.class,
TextInputChannelTest.class,
TextInputPluginTest.class,
})
Expand Down
Loading