summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Yohei Yukawa <yukawa@google.com> 2016-01-13 22:48:14 -0800
committer Yohei Yukawa <yukawa@google.com> 2016-01-13 22:48:14 -0800
commitc89e22a6ff227089fde26daea186346029d1b32c (patch)
tree7e35fffa8eedb1225e4b34ca139796bc819bfdff
parenta1dcb87565ab9a34ae14efebe4743701068cffad (diff)
Introdude IC#deleteSurroundingTextInCodePoints().
This CL introduces a API variant of IC#deleteSurroundingText(), named IC#deleteSurroundingTextInCodePoints(). Major differences from the existing one are: - The lengths are supplied in code points rather than code units. - This API does nothing if there are one or more invalid surrogate pairs in the requested range. (Failure Atomicity) Note that due to the asynchronous nature of the input method architecture in Android, implementing the same logic in the input method side basically ends up with unreliable and unpredictable results. Bug: 6526420 Change-Id: I7f6a2c5d3d52079ae71623fd5e40d60c688dd5fb
-rw-r--r--api/current.txt3
-rw-r--r--api/system-current.txt3
-rw-r--r--api/test-current.txt3
-rw-r--r--core/java/android/view/inputmethod/BaseInputConnection.java163
-rw-r--r--core/java/android/view/inputmethod/InputConnection.java27
-rw-r--r--core/java/android/view/inputmethod/InputConnectionWrapper.java4
-rw-r--r--core/java/android/widget/AbsListView.java5
-rw-r--r--core/java/com/android/internal/view/IInputConnectionWrapper.java21
-rw-r--r--core/java/com/android/internal/view/IInputContext.aidl5
-rw-r--r--core/java/com/android/internal/view/InputConnectionWrapper.java11
10 files changed, 238 insertions, 7 deletions
diff --git a/api/current.txt b/api/current.txt
index 629d832c0c3c..c2e6ff3140c5 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -43334,6 +43334,7 @@ package android.view.inputmethod {
method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
method public boolean commitText(java.lang.CharSequence, int);
method public boolean deleteSurroundingText(int, int);
+ method public boolean deleteSurroundingTextInCodePoints(int, int);
method public boolean endBatchEdit();
method public boolean finishComposingText();
method public static int getComposingSpanEnd(android.text.Spannable);
@@ -43499,6 +43500,7 @@ package android.view.inputmethod {
method public abstract boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
method public abstract boolean commitText(java.lang.CharSequence, int);
method public abstract boolean deleteSurroundingText(int, int);
+ method public abstract boolean deleteSurroundingTextInCodePoints(int, int);
method public abstract boolean endBatchEdit();
method public abstract boolean finishComposingText();
method public abstract int getCursorCapsMode(int);
@@ -43529,6 +43531,7 @@ package android.view.inputmethod {
method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
method public boolean commitText(java.lang.CharSequence, int);
method public boolean deleteSurroundingText(int, int);
+ method public boolean deleteSurroundingTextInCodePoints(int, int);
method public boolean endBatchEdit();
method public boolean finishComposingText();
method public int getCursorCapsMode(int);
diff --git a/api/system-current.txt b/api/system-current.txt
index 6a36ce05acca..3ec14c8a4b37 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -45688,6 +45688,7 @@ package android.view.inputmethod {
method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
method public boolean commitText(java.lang.CharSequence, int);
method public boolean deleteSurroundingText(int, int);
+ method public boolean deleteSurroundingTextInCodePoints(int, int);
method public boolean endBatchEdit();
method public boolean finishComposingText();
method public static int getComposingSpanEnd(android.text.Spannable);
@@ -45853,6 +45854,7 @@ package android.view.inputmethod {
method public abstract boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
method public abstract boolean commitText(java.lang.CharSequence, int);
method public abstract boolean deleteSurroundingText(int, int);
+ method public abstract boolean deleteSurroundingTextInCodePoints(int, int);
method public abstract boolean endBatchEdit();
method public abstract boolean finishComposingText();
method public abstract int getCursorCapsMode(int);
@@ -45883,6 +45885,7 @@ package android.view.inputmethod {
method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
method public boolean commitText(java.lang.CharSequence, int);
method public boolean deleteSurroundingText(int, int);
+ method public boolean deleteSurroundingTextInCodePoints(int, int);
method public boolean endBatchEdit();
method public boolean finishComposingText();
method public int getCursorCapsMode(int);
diff --git a/api/test-current.txt b/api/test-current.txt
index 21b101fef449..bc18278233b9 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -43350,6 +43350,7 @@ package android.view.inputmethod {
method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
method public boolean commitText(java.lang.CharSequence, int);
method public boolean deleteSurroundingText(int, int);
+ method public boolean deleteSurroundingTextInCodePoints(int, int);
method public boolean endBatchEdit();
method public boolean finishComposingText();
method public static int getComposingSpanEnd(android.text.Spannable);
@@ -43515,6 +43516,7 @@ package android.view.inputmethod {
method public abstract boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
method public abstract boolean commitText(java.lang.CharSequence, int);
method public abstract boolean deleteSurroundingText(int, int);
+ method public abstract boolean deleteSurroundingTextInCodePoints(int, int);
method public abstract boolean endBatchEdit();
method public abstract boolean finishComposingText();
method public abstract int getCursorCapsMode(int);
@@ -43545,6 +43547,7 @@ package android.view.inputmethod {
method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
method public boolean commitText(java.lang.CharSequence, int);
method public boolean deleteSurroundingText(int, int);
+ method public boolean deleteSurroundingTextInCodePoints(int, int);
method public boolean endBatchEdit();
method public boolean finishComposingText();
method public int getCursorCapsMode(int);
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index 6e5e5915bb6f..5919f9e89669 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -230,7 +230,7 @@ public class BaseInputConnection implements InputConnection {
b = tmp;
}
- // ignore the composing text.
+ // Ignore the composing text.
int ca = getComposingSpanStart(content);
int cb = getComposingSpanEnd(content);
if (cb < ca) {
@@ -266,6 +266,167 @@ public class BaseInputConnection implements InputConnection {
return true;
}
+ private static int INVALID_INDEX = -1;
+ private static int findIndexBackward(final CharSequence cs, final int from,
+ final int numCodePoints) {
+ int currentIndex = from;
+ boolean waitingHighSurrogate = false;
+ final int N = cs.length();
+ if (currentIndex < 0 || N < currentIndex) {
+ return INVALID_INDEX; // The starting point is out of range.
+ }
+ if (numCodePoints < 0) {
+ return INVALID_INDEX; // Basically this should not happen.
+ }
+ int remainingCodePoints = numCodePoints;
+ while (true) {
+ if (remainingCodePoints == 0) {
+ return currentIndex; // Reached to the requested length in code points.
+ }
+
+ --currentIndex;
+ if (currentIndex < 0) {
+ if (waitingHighSurrogate) {
+ return INVALID_INDEX; // An invalid surrogate pair is found.
+ }
+ return 0; // Reached to the beginning of the text w/o any invalid surrogate pair.
+ }
+ final char c = cs.charAt(currentIndex);
+ if (waitingHighSurrogate) {
+ if (!java.lang.Character.isHighSurrogate(c)) {
+ return INVALID_INDEX; // An invalid surrogate pair is found.
+ }
+ waitingHighSurrogate = false;
+ --remainingCodePoints;
+ continue;
+ }
+ if (!java.lang.Character.isSurrogate(c)) {
+ --remainingCodePoints;
+ continue;
+ }
+ if (java.lang.Character.isHighSurrogate(c)) {
+ return INVALID_INDEX; // A invalid surrogate pair is found.
+ }
+ waitingHighSurrogate = true;
+ }
+ }
+
+ private static int findIndexForward(final CharSequence cs, final int from,
+ final int numCodePoints) {
+ int currentIndex = from;
+ boolean waitingLowSurrogate = false;
+ final int N = cs.length();
+ if (currentIndex < 0 || N < currentIndex) {
+ return INVALID_INDEX; // The starting point is out of range.
+ }
+ if (numCodePoints < 0) {
+ return INVALID_INDEX; // Basically this should not happen.
+ }
+ int remainingCodePoints = numCodePoints;
+
+ while (true) {
+ if (remainingCodePoints == 0) {
+ return currentIndex; // Reached to the requested length in code points.
+ }
+
+ if (currentIndex >= N) {
+ if (waitingLowSurrogate) {
+ return INVALID_INDEX; // An invalid surrogate pair is found.
+ }
+ return N; // Reached to the end of the text w/o any invalid surrogate pair.
+ }
+ final char c = cs.charAt(currentIndex);
+ if (waitingLowSurrogate) {
+ if (!java.lang.Character.isLowSurrogate(c)) {
+ return INVALID_INDEX; // An invalid surrogate pair is found.
+ }
+ --remainingCodePoints;
+ waitingLowSurrogate = false;
+ ++currentIndex;
+ continue;
+ }
+ if (!java.lang.Character.isSurrogate(c)) {
+ --remainingCodePoints;
+ ++currentIndex;
+ continue;
+ }
+ if (java.lang.Character.isLowSurrogate(c)) {
+ return INVALID_INDEX; // A invalid surrogate pair is found.
+ }
+ waitingLowSurrogate = true;
+ ++currentIndex;
+ }
+ }
+
+ /**
+ * The default implementation performs the deletion around the current selection position of the
+ * editable text.
+ * @param beforeLength The number of characters before the cursor to be deleted, in code points.
+ * If this is greater than the number of existing characters between the beginning of the
+ * text and the cursor, then this method does not fail but deletes all the characters in
+ * that range.
+ * @param afterLength The number of characters after the cursor to be deleted, in code points.
+ * If this is greater than the number of existing characters between the cursor and
+ * the end of the text, then this method does not fail but deletes all the characters in
+ * that range.
+ */
+ public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
+ if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength
+ + " / " + afterLength);
+ final Editable content = getEditable();
+ if (content == null) return false;
+
+ beginBatchEdit();
+
+ int a = Selection.getSelectionStart(content);
+ int b = Selection.getSelectionEnd(content);
+
+ if (a > b) {
+ int tmp = a;
+ a = b;
+ b = tmp;
+ }
+
+ // Ignore the composing text.
+ int ca = getComposingSpanStart(content);
+ int cb = getComposingSpanEnd(content);
+ if (cb < ca) {
+ int tmp = ca;
+ ca = cb;
+ cb = tmp;
+ }
+ if (ca != -1 && cb != -1) {
+ if (ca < a) a = ca;
+ if (cb > b) b = cb;
+ }
+
+ if (a >= 0 && b >= 0) {
+ final int start = findIndexBackward(content, a, Math.max(beforeLength, 0));
+ if (start != INVALID_INDEX) {
+ final int end = findIndexForward(content, b, Math.max(afterLength, 0));
+ if (end != INVALID_INDEX) {
+ final int numDeleteBefore = a - start;
+ if (numDeleteBefore > 0) {
+ content.delete(start, a);
+ }
+ final int numDeleteAfter = end - b;
+ if (numDeleteAfter > 0) {
+ content.delete(b - numDeleteBefore, end - numDeleteBefore);
+ }
+ }
+ }
+ // NOTE: You may think we should return false here if start and/or end is INVALID_INDEX,
+ // but the truth is that IInputConnectionWrapper running in the middle of IPC calls
+ // always returns true to the IME without waiting for the completion of this method as
+ // IInputConnectionWrapper#isAtive() returns true. This is actually why some methods
+ // including this method look like asynchronous calls from the IME.
+ }
+
+ endBatchEdit();
+
+ return true;
+ }
+
/**
* The default implementation removes the composing state from the
* current editable text. In addition, only if dummy mode, a key event is
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index be7bc14f0515..eb773e23fa62 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -348,6 +348,33 @@ public interface InputConnection {
public boolean deleteSurroundingText(int beforeLength, int afterLength);
/**
+ * A variant of {@link #deleteSurroundingText(int, int)}. Major differences are:
+ *
+ * <ul>
+ * <li>The lengths are supplied in code points, not in Java chars or in glyphs.</>
+ * <li>This method does nothing if there are one or more invalid surrogate pairs in the
+ * requested range.</li>
+ * </ul>
+ *
+ * <p><strong>Editor authors:</strong> In addition to the requirement in
+ * {@link #deleteSurroundingText(int, int)}, make sure to do nothing when one ore more invalid
+ * surrogate pairs are found in the requested range.</p>
+ *
+ * @see #deleteSurroundingText(int, int)
+ *
+ * @param beforeLength The number of characters before the cursor to be deleted, in code points.
+ * If this is greater than the number of existing characters between the beginning of the
+ * text and the cursor, then this method does not fail but deletes all the characters in
+ * that range.
+ * @param afterLength The number of characters after the cursor to be deleted, in code points.
+ * If this is greater than the number of existing characters between the cursor and
+ * the end of the text, then this method does not fail but deletes all the characters in
+ * that range.
+ * @return true on success, false if the input connection is no longer valid.
+ */
+ public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength);
+
+ /**
* Replace the currently composing text with the given text, and
* set the new cursor position. Any composing text set previously
* will be removed automatically.
diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java
index 231aa070672a..e5ae42299dba 100644
--- a/core/java/android/view/inputmethod/InputConnectionWrapper.java
+++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java
@@ -62,6 +62,10 @@ public class InputConnectionWrapper implements InputConnection {
return mTarget.getExtractedText(request, flags);
}
+ public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
+ return mTarget.deleteSurroundingTextInCodePoints(beforeLength, afterLength);
+ }
+
public boolean deleteSurroundingText(int beforeLength, int afterLength) {
return mTarget.deleteSurroundingText(beforeLength, afterLength);
}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index e1ce9fe7c039..6241a4cd3462 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -5803,6 +5803,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
@Override
+ public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
+ return getTarget().deleteSurroundingTextInCodePoints(beforeLength, afterLength);
+ }
+
+ @Override
public boolean setComposingText(CharSequence text, int newCursorPosition) {
return getTarget().setComposingText(text, newCursorPosition);
}
diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java
index 85ec29c4634c..b77cc6365d8a 100644
--- a/core/java/com/android/internal/view/IInputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java
@@ -49,6 +49,7 @@ public class IInputConnectionWrapper extends IInputContext.Stub {
private static final int DO_FINISH_COMPOSING_TEXT = 65;
private static final int DO_SEND_KEY_EVENT = 70;
private static final int DO_DELETE_SURROUNDING_TEXT = 80;
+ private static final int DO_DELETE_SURROUNDING_TEXT_IN_CODE_POINTS = 81;
private static final int DO_BEGIN_BATCH_EDIT = 90;
private static final int DO_END_BATCH_EDIT = 95;
private static final int DO_REPORT_FULLSCREEN_MODE = 100;
@@ -155,9 +156,14 @@ public class IInputConnectionWrapper extends IInputContext.Stub {
dispatchMessage(obtainMessageII(DO_CLEAR_META_KEY_STATES, states, 0));
}
- public void deleteSurroundingText(int leftLength, int rightLength) {
+ public void deleteSurroundingText(int beforeLength, int afterLength) {
dispatchMessage(obtainMessageII(DO_DELETE_SURROUNDING_TEXT,
- leftLength, rightLength));
+ beforeLength, afterLength));
+ }
+
+ public void deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
+ dispatchMessage(obtainMessageII(DO_DELETE_SURROUNDING_TEXT_IN_CODE_POINTS,
+ beforeLength, afterLength));
}
public void beginBatchEdit() {
@@ -389,6 +395,15 @@ public class IInputConnectionWrapper extends IInputContext.Stub {
ic.deleteSurroundingText(msg.arg1, msg.arg2);
return;
}
+ case DO_DELETE_SURROUNDING_TEXT_IN_CODE_POINTS: {
+ InputConnection ic = mInputConnection.get();
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "deleteSurroundingTextInCodePoints on inactive InputConnection");
+ return;
+ }
+ ic.deleteSurroundingTextInCodePoints(msg.arg1, msg.arg2);
+ return;
+ }
case DO_BEGIN_BATCH_EDIT: {
InputConnection ic = mInputConnection.get();
if (ic == null || !isActive()) {
@@ -454,7 +469,7 @@ public class IInputConnectionWrapper extends IInputContext.Stub {
Message obtainMessageII(int what, int arg1, int arg2) {
return mH.obtainMessage(what, arg1, arg2);
}
-
+
Message obtainMessageO(int what, Object arg1) {
return mH.obtainMessage(what, 0, 0, arg1);
}
diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl
index fd2b513fcb1a..f7ec420ba676 100644
--- a/core/java/com/android/internal/view/IInputContext.aidl
+++ b/core/java/com/android/internal/view/IInputContext.aidl
@@ -38,8 +38,9 @@ import com.android.internal.view.IInputContextCallback;
void getExtractedText(in ExtractedTextRequest request, int flags, int seq,
IInputContextCallback callback);
-
- void deleteSurroundingText(int leftLength, int rightLength);
+
+ void deleteSurroundingText(int beforeLength, int afterLength);
+ void deleteSurroundingTextInCodePoints(int beforeLength, int afterLength);
void setComposingText(CharSequence text, int newCursorPosition);
diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java
index 7dc927f744f6..94790c158559 100644
--- a/core/java/com/android/internal/view/InputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/InputConnectionWrapper.java
@@ -400,7 +400,7 @@ public class InputConnectionWrapper implements InputConnection {
return false;
}
}
-
+
public boolean deleteSurroundingText(int beforeLength, int afterLength) {
try {
mIInputContext.deleteSurroundingText(beforeLength, afterLength);
@@ -410,6 +410,15 @@ public class InputConnectionWrapper implements InputConnection {
}
}
+ public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
+ try {
+ mIInputContext.deleteSurroundingTextInCodePoints(beforeLength, afterLength);
+ return true;
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
public boolean reportFullscreenMode(boolean enabled) {
try {
mIInputContext.reportFullscreenMode(enabled);