Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,42 @@
/** Exercises the production connection against Android's real Editable/InputConnection code. */
@SuppressWarnings("deprecation")
public final class GpuiInputConnectionTest extends AndroidTestCase {
private static final class FakeClipboard implements GpuiInputConnection.Clipboard {
String text;
@Override public String read() { return text; }
@Override public void write(String value) { text = value; }
}

public void testImeEditPanelActionsEditTheDraftAndClipboard() {
SpannableStringBuilder text = new SpannableStringBuilder("hello 世界");
Selection.setSelection(text, 0);
ArrayList<GpuiInputConnection.State> sent = new ArrayList<>();
FakeClipboard clipboard = new FakeClipboard();
GpuiInputConnection input =
new GpuiInputConnection(new View(getContext()), text, clipboard, sent::add);
assertFalse(input.performContextMenuAction(android.R.id.paste));
assertFalse(input.performContextMenuAction(android.R.id.copy));
assertTrue(sent.isEmpty());
assertTrue(input.performContextMenuAction(android.R.id.selectAll));
assertEquals(new GpuiInputConnection.State("hello 世界", 0, 8, -1, -1), sent.get(0));
assertTrue(input.performContextMenuAction(android.R.id.copy));
assertEquals("hello 世界", clipboard.text);
assertEquals(1, sent.size());
input.setSelection(6, 8);
assertTrue(input.performContextMenuAction(android.R.id.cut));
assertEquals("世界", clipboard.text);
assertEquals(new GpuiInputConnection.State("hello ", 6, 6, -1, -1), sent.get(sent.size() - 1));
clipboard.text = "again";
assertTrue(input.performContextMenuAction(android.R.id.paste));
assertEquals(new GpuiInputConnection.State("hello again", 11, 11, -1, -1), sent.get(sent.size() - 1));
}

public void testAutocompletePublishesTheCompleteReplacementOnce() {
for (boolean composing : new boolean[] {false, true}) {
SpannableStringBuilder text = new SpannableStringBuilder("an exmple here");
Selection.setSelection(text, 9);
ArrayList<GpuiInputConnection.State> sent = new ArrayList<>();
GpuiInputConnection input = new GpuiInputConnection(new View(getContext()), text, sent::add);
GpuiInputConnection input = new GpuiInputConnection(new View(getContext()), text, new FakeClipboard(), sent::add);
input.beginBatchEdit();
if (composing) {
input.setComposingRegion(3, 9);
Expand All @@ -33,7 +63,7 @@ public void testSelectionAndUnicodeDeletionReachTheComposer() {
SpannableStringBuilder text = new SpannableStringBuilder("😀word中");
Selection.setSelection(text, text.length());
ArrayList<GpuiInputConnection.State> sent = new ArrayList<>();
GpuiInputConnection input = new GpuiInputConnection(new View(getContext()), text, sent::add);
GpuiInputConnection input = new GpuiInputConnection(new View(getContext()), text, new FakeClipboard(), sent::add);
input.setSelection(2, 6);
assertEquals(new GpuiInputConnection.State("😀word中", 2, 6, -1, -1), sent.get(0));
input.commitText("example", 0);
Expand All @@ -48,7 +78,7 @@ public void testRetiredConnectionCannotChangeTheNextDraft() {
SpannableStringBuilder text = new SpannableStringBuilder("old");
Selection.setSelection(text, text.length());
ArrayList<GpuiInputConnection.State> sent = new ArrayList<>();
GpuiInputConnection input = new GpuiInputConnection(new View(getContext()), text, sent::add);
GpuiInputConnection input = new GpuiInputConnection(new View(getContext()), text, new FakeClipboard(), sent::add);
input.closeConnection();
text.replace(0, text.length(), "new");
input.commitText("stale suggestion", 1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,11 @@ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
outAttrs.initialSelStart = Selection.getSelectionStart(editable);
outAttrs.initialSelEnd = Selection.getSelectionEnd(editable);
if (connection != null) connection.closeConnection();
connection = new GpuiInputConnection(this, editable, this::publishInput) {
GpuiInputConnection.Clipboard clipboard = new GpuiInputConnection.Clipboard() {
@Override public String read() { return gpuiReadClipboard(); }
@Override public void write(String text) { gpuiWriteClipboard(text); }
};
connection = new GpuiInputConnection(this, editable, clipboard, this::publishInput) {
@Override public boolean commitText(CharSequence text, int cursor) {
if (getEditable() == null) return false;
if (!textEditable) nativeCommitText(text.toString());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,21 @@
/** Publish Android's completed edits, including selection and composing-span changes. */
class GpuiInputConnection extends BaseInputConnection {
record State(String text, int selectionStart, int selectionEnd, int composingStart, int composingEnd) {}
interface Clipboard {
String read();
void write(String text);
}
private final Editable editable;
private final Clipboard clipboard;
private final Consumer<State> changed;
private int batchDepth;
private boolean closed;
private State lastState;

GpuiInputConnection(View view, Editable editable, Consumer<State> changed) {
GpuiInputConnection(View view, Editable editable, Clipboard clipboard, Consumer<State> changed) {
super(view, true);
this.editable = editable;
this.clipboard = clipboard;
this.changed = changed;
rememberState();
}
Expand Down Expand Up @@ -48,6 +54,28 @@ record State(String text, int selectionStart, int selectionEnd, int composingSta
return result;
}

// IME edit panels (select all, copy, cut, paste) arrive here; BaseInputConnection ignores them.
@Override public boolean performContextMenuAction(int id) {
if (closed) return false;
if (id == android.R.id.selectAll) return setSelection(0, editable.length());
if (id == android.R.id.paste) {
String text = clipboard.read();
return text != null && commitText(text, 1);
}
int start = Math.min(Selection.getSelectionStart(editable), Selection.getSelectionEnd(editable));
int end = Math.max(Selection.getSelectionStart(editable), Selection.getSelectionEnd(editable));
if ((id != android.R.id.copy && id != android.R.id.cut) || start < 0 || start == end) return false;
clipboard.write(editable.subSequence(start, end).toString());
if (id == android.R.id.cut) {
beginBatchEdit();
removeComposingSpans(editable);
editable.delete(start, end);
Selection.setSelection(editable, start);
endBatchEdit();
}
return true;
}

void publishChanges() {
if (closed || batchDepth != 0) return;
State state = readState();
Expand Down
Loading