Permalink
Browse files

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
  • Loading branch information...
1 parent a1dcb87 commit c89e22a6ff227089fde26daea186346029d1b32c @yukawa yukawa committed Jan 14, 2016
View
@@ -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);
@@ -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);
@@ -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);
@@ -230,7 +230,7 @@ public boolean deleteSurroundingText(int beforeLength, int afterLength) {
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 boolean deleteSurroundingText(int beforeLength, int afterLength) {
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
@@ -348,6 +348,33 @@ public ExtractedText getExtractedText(ExtractedTextRequest request,
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.
@@ -62,6 +62,10 @@ public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
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);
}
@@ -5803,6 +5803,11 @@ public boolean deleteSurroundingText(int beforeLength, int afterLength) {
}
@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);
}
@@ -49,6 +49,7 @@
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 void clearMetaKeyStates(int states) {
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 @@ void executeMessage(Message msg) {
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 @@ Message obtainMessage(int what) {
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);
}
@@ -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);
@@ -400,7 +400,7 @@ public boolean clearMetaKeyStates(int states) {
return false;
}
}
-
+
public boolean deleteSurroundingText(int beforeLength, int afterLength) {
try {
mIInputContext.deleteSurroundingText(beforeLength, afterLength);
@@ -410,6 +410,15 @@ public boolean deleteSurroundingText(int beforeLength, int afterLength) {
}
}
+ 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);

0 comments on commit c89e22a

Please sign in to comment.