Loading core/java/android/text/Emoji.java +11 −0 Original line number Diff line number Diff line Loading @@ -164,6 +164,8 @@ public class Emoji { public static int VARIATION_SELECTOR_16 = 0xFE0F; public static int CANCEL_TAG = 0xE007F; // Returns true if the given code point is regional indicator symbol. public static boolean isRegionalIndicatorSymbol(int codepoint) { return 0x1F1E6 <= codepoint && codepoint <= 0x1F1FF; Loading @@ -188,4 +190,13 @@ public class Emoji { public static boolean isKeycapBase(int codePoint) { return ('0' <= codePoint && codePoint <= '9') || codePoint == '#' || codePoint == '*'; } /** * Returns true if the character can be a part of tag_spec in emoji tag sequence. * * Note that 0xE007F (CANCEL TAG) is not included. */ public static boolean isTagSpecChar(int codePoint) { return 0xE0020 <= codePoint && codePoint <= 0xE007E; } } core/java/android/text/method/BaseKeyListener.java +20 −1 Original line number Diff line number Diff line Loading @@ -145,8 +145,11 @@ public abstract class BaseKeyListener extends MetaKeyKeyListener // The number of following RIS code points is even. final int STATE_EVEN_NUMBERED_RIS = 11; // The offset is in emoji tag sequence. final int STATE_IN_TAG_SEQUENCE = 12; // The state machine has been stopped. final int STATE_FINISHED = 12; final int STATE_FINISHED = 13; int deleteCharCount = 0; // Char count to be deleted by backspace. int lastSeenVSCharCount = 0; // Char count of previous variation selector. Loading @@ -173,6 +176,8 @@ public abstract class BaseKeyListener extends MetaKeyKeyListener state = STATE_BEFORE_KEYCAP; } else if (Emoji.isEmoji(codePoint)) { state = STATE_BEFORE_EMOJI; } else if (codePoint == Emoji.CANCEL_TAG) { state = STATE_IN_TAG_SEQUENCE; } else { state = STATE_FINISHED; } Loading Loading @@ -275,6 +280,20 @@ public abstract class BaseKeyListener extends MetaKeyKeyListener state = STATE_FINISHED; } break; case STATE_IN_TAG_SEQUENCE: if (Emoji.isTagSpecChar(codePoint)) { deleteCharCount += 2; /* Char count of emoji tag spec character. */ // Keep the same state. } else if (Emoji.isEmoji(codePoint)) { deleteCharCount += Character.charCount(codePoint); state = STATE_FINISHED; } else { // Couldn't find tag_base character. Delete the last tag_term character. deleteCharCount = 2; // for U+E007F state = STATE_FINISHED; } // TODO: Need handle emoji variation selectors. Issue 35224297 break; default: throw new IllegalArgumentException("state " + state + " is unknown"); } Loading core/tests/coretests/src/android/text/method/BackspaceTest.java +49 −15 Original line number Diff line number Diff line Loading @@ -37,23 +37,12 @@ public class BackspaceTest extends KeyListenerTestCase { // Sync the state to the TextView and call onKeyDown with KEYCODE_DEL key event. // Then update the state to the result of TextView. private void backspace(final EditorState state, int modifiers) { mActivity.runOnUiThread(new Runnable() { public void run() { mTextView.setText(state.mText, BufferType.EDITABLE); mTextView.setKeyListener(mKeyListener); mTextView.setSelection(state.mSelectionStart, state.mSelectionEnd); } }); mInstrumentation.waitForIdleSync(); assertTrue(mTextView.hasWindowFocus()); final KeyEvent keyEvent = getKey(KeyEvent.KEYCODE_DEL, modifiers); mActivity.runOnUiThread(new Runnable() { public void run() { mTextView.onKeyDown(keyEvent.getKeyCode(), keyEvent); } }); mInstrumentation.waitForIdleSync(); state.mText = mTextView.getText(); state.mSelectionStart = mTextView.getSelectionStart(); Loading Loading @@ -247,6 +236,51 @@ public class BackspaceTest extends KeyListenerTestCase { state.assertEquals("U+1F1FA U+1F1F8 |"); backspace(state, 0); state.assertEquals("|"); // Incomplete sequence. (no tag_term: U+E007E) state.setByString("'a' U+1F3F4 U+E0067 'b' |"); backspace(state, 0); state.assertEquals("'a' U+1F3F4 U+E0067 |"); backspace(state, 0); state.assertEquals("'a' U+1F3F4 |"); backspace(state, 0); state.assertEquals("'a' |"); // No tag_base state.setByString("'a' U+E0067 U+E007F 'b' |"); backspace(state, 0); state.assertEquals("'a' U+E0067 U+E007F |"); backspace(state, 0); state.assertEquals("'a' U+E0067 |"); backspace(state, 0); state.assertEquals("'a' |"); // Isolated tag chars state.setByString("'a' U+E0067 U+E0067 'b' |"); backspace(state, 0); state.assertEquals("'a' U+E0067 U+E0067 |"); backspace(state, 0); state.assertEquals("'a' U+E0067 |"); backspace(state, 0); state.assertEquals("'a' |"); // Isolated tab term. state.setByString("'a' U+E007F U+E007F 'b' |"); backspace(state, 0); state.assertEquals("'a' U+E007F U+E007F |"); backspace(state, 0); state.assertEquals("'a' U+E007F |"); backspace(state, 0); state.assertEquals("'a' |"); // Immediate tag_term after tag_base state.setByString("'a' U+1F3F4 U+E007F U+1F3F4 U+E007F 'b' |"); backspace(state, 0); state.assertEquals("'a' U+1F3F4 U+E007F U+1F3F4 U+E007F |"); backspace(state, 0); state.assertEquals("'a' U+1F3F4 U+E007F |"); backspace(state, 0); state.assertEquals("'a' |"); } @SmallTest Loading core/tests/coretests/src/android/text/method/ForwardDeleteTest.java +44 −15 Original line number Diff line number Diff line Loading @@ -37,23 +37,12 @@ public class ForwardDeleteTest extends KeyListenerTestCase { // Sync the state to the TextView and call onKeyDown with KEYCODE_FORWARD_DEL key event. // Then update the state to the result of TextView. private void forwardDelete(final EditorState state, int modifiers) { mActivity.runOnUiThread(new Runnable() { public void run() { mTextView.setText(state.mText, BufferType.EDITABLE); mTextView.setKeyListener(mKeyListener); mTextView.setSelection(state.mSelectionStart, state.mSelectionEnd); } }); mInstrumentation.waitForIdleSync(); assertTrue(mTextView.hasWindowFocus()); final KeyEvent keyEvent = getKey(KeyEvent.KEYCODE_FORWARD_DEL, modifiers); mActivity.runOnUiThread(new Runnable() { public void run() { mTextView.onKeyDown(keyEvent.getKeyCode(), keyEvent); } }); mInstrumentation.waitForIdleSync(); state.mText = mTextView.getText(); state.mSelectionStart = mTextView.getSelectionStart(); Loading Loading @@ -186,6 +175,46 @@ public class ForwardDeleteTest extends KeyListenerTestCase { state.assertEquals("| U+1F1FA"); forwardDelete(state, 0); state.assertEquals("|"); // Incomplete sequence. (no tag_term:U+E007E) state.setByString("| 'a' U+1F3F4 U+E0067 'b'"); forwardDelete(state, 0); state.assertEquals("| U+1F3F4 U+E0067 'b'"); forwardDelete(state, 0); state.assertEquals("| 'b'"); // No tag_base state.setByString("| 'a' U+E0067 U+E007F 'b'"); forwardDelete(state, 0); state.assertEquals("| 'b'"); // Isolated tag chars state.setByString("| 'a' U+E0067 U+E0067 'b'"); forwardDelete(state, 0); state.assertEquals("| 'b'"); // Isolated tag base. state.setByString("| 'a' U+1F3F4 U+1F3F4 'b'"); forwardDelete(state, 0); state.assertEquals("| U+1F3F4 U+1F3F4 'b'"); forwardDelete(state, 0); state.assertEquals("| U+1F3F4 'b'"); forwardDelete(state, 0); state.assertEquals("| 'b'"); // Isolated tab term. state.setByString("| 'a' U+E007F U+E007F 'b'"); forwardDelete(state, 0); state.assertEquals("| 'b'"); // Immediate tag_term after tag_base state.setByString("| 'a' U+1F3F4 U+E007F U+1F3F4 U+E007F 'b'"); forwardDelete(state, 0); state.assertEquals("| U+1F3F4 U+E007F U+1F3F4 U+E007F 'b'"); forwardDelete(state, 0); state.assertEquals("| U+1F3F4 U+E007F 'b'"); forwardDelete(state, 0); state.assertEquals("| 'b'"); } @SmallTest Loading core/tests/coretests/src/android/text/method/KeyListenerTestCase.java +3 −18 Original line number Diff line number Diff line Loading @@ -17,41 +17,26 @@ package android.text.method; import android.app.Instrumentation; import android.test.ActivityInstrumentationTestCase2; import android.text.format.DateUtils; import android.test.InstrumentationTestCase; import android.view.KeyEvent; import android.widget.EditText; import android.widget.TextViewActivity; import com.android.frameworks.coretests.R; public abstract class KeyListenerTestCase extends ActivityInstrumentationTestCase2<TextViewActivity> { public abstract class KeyListenerTestCase extends InstrumentationTestCase { protected TextViewActivity mActivity; protected Instrumentation mInstrumentation; protected EditText mTextView; public KeyListenerTestCase() { super(TextViewActivity.class); } @Override protected void setUp() throws Exception { super.setUp(); mActivity = getActivity(); mInstrumentation = getInstrumentation(); mTextView = (EditText) mActivity.findViewById(R.id.textview); mActivity.runOnUiThread(new Runnable() { public void run() { // Ensure that the screen is on for this test. mTextView.setKeepScreenOn(true); } }); assertTrue(mActivity.waitForWindowFocus(5 * DateUtils.SECOND_IN_MILLIS)); mTextView = new EditText(mInstrumentation.getContext()); } protected static KeyEvent getKey(int keycode, int metaState) { Loading Loading
core/java/android/text/Emoji.java +11 −0 Original line number Diff line number Diff line Loading @@ -164,6 +164,8 @@ public class Emoji { public static int VARIATION_SELECTOR_16 = 0xFE0F; public static int CANCEL_TAG = 0xE007F; // Returns true if the given code point is regional indicator symbol. public static boolean isRegionalIndicatorSymbol(int codepoint) { return 0x1F1E6 <= codepoint && codepoint <= 0x1F1FF; Loading @@ -188,4 +190,13 @@ public class Emoji { public static boolean isKeycapBase(int codePoint) { return ('0' <= codePoint && codePoint <= '9') || codePoint == '#' || codePoint == '*'; } /** * Returns true if the character can be a part of tag_spec in emoji tag sequence. * * Note that 0xE007F (CANCEL TAG) is not included. */ public static boolean isTagSpecChar(int codePoint) { return 0xE0020 <= codePoint && codePoint <= 0xE007E; } }
core/java/android/text/method/BaseKeyListener.java +20 −1 Original line number Diff line number Diff line Loading @@ -145,8 +145,11 @@ public abstract class BaseKeyListener extends MetaKeyKeyListener // The number of following RIS code points is even. final int STATE_EVEN_NUMBERED_RIS = 11; // The offset is in emoji tag sequence. final int STATE_IN_TAG_SEQUENCE = 12; // The state machine has been stopped. final int STATE_FINISHED = 12; final int STATE_FINISHED = 13; int deleteCharCount = 0; // Char count to be deleted by backspace. int lastSeenVSCharCount = 0; // Char count of previous variation selector. Loading @@ -173,6 +176,8 @@ public abstract class BaseKeyListener extends MetaKeyKeyListener state = STATE_BEFORE_KEYCAP; } else if (Emoji.isEmoji(codePoint)) { state = STATE_BEFORE_EMOJI; } else if (codePoint == Emoji.CANCEL_TAG) { state = STATE_IN_TAG_SEQUENCE; } else { state = STATE_FINISHED; } Loading Loading @@ -275,6 +280,20 @@ public abstract class BaseKeyListener extends MetaKeyKeyListener state = STATE_FINISHED; } break; case STATE_IN_TAG_SEQUENCE: if (Emoji.isTagSpecChar(codePoint)) { deleteCharCount += 2; /* Char count of emoji tag spec character. */ // Keep the same state. } else if (Emoji.isEmoji(codePoint)) { deleteCharCount += Character.charCount(codePoint); state = STATE_FINISHED; } else { // Couldn't find tag_base character. Delete the last tag_term character. deleteCharCount = 2; // for U+E007F state = STATE_FINISHED; } // TODO: Need handle emoji variation selectors. Issue 35224297 break; default: throw new IllegalArgumentException("state " + state + " is unknown"); } Loading
core/tests/coretests/src/android/text/method/BackspaceTest.java +49 −15 Original line number Diff line number Diff line Loading @@ -37,23 +37,12 @@ public class BackspaceTest extends KeyListenerTestCase { // Sync the state to the TextView and call onKeyDown with KEYCODE_DEL key event. // Then update the state to the result of TextView. private void backspace(final EditorState state, int modifiers) { mActivity.runOnUiThread(new Runnable() { public void run() { mTextView.setText(state.mText, BufferType.EDITABLE); mTextView.setKeyListener(mKeyListener); mTextView.setSelection(state.mSelectionStart, state.mSelectionEnd); } }); mInstrumentation.waitForIdleSync(); assertTrue(mTextView.hasWindowFocus()); final KeyEvent keyEvent = getKey(KeyEvent.KEYCODE_DEL, modifiers); mActivity.runOnUiThread(new Runnable() { public void run() { mTextView.onKeyDown(keyEvent.getKeyCode(), keyEvent); } }); mInstrumentation.waitForIdleSync(); state.mText = mTextView.getText(); state.mSelectionStart = mTextView.getSelectionStart(); Loading Loading @@ -247,6 +236,51 @@ public class BackspaceTest extends KeyListenerTestCase { state.assertEquals("U+1F1FA U+1F1F8 |"); backspace(state, 0); state.assertEquals("|"); // Incomplete sequence. (no tag_term: U+E007E) state.setByString("'a' U+1F3F4 U+E0067 'b' |"); backspace(state, 0); state.assertEquals("'a' U+1F3F4 U+E0067 |"); backspace(state, 0); state.assertEquals("'a' U+1F3F4 |"); backspace(state, 0); state.assertEquals("'a' |"); // No tag_base state.setByString("'a' U+E0067 U+E007F 'b' |"); backspace(state, 0); state.assertEquals("'a' U+E0067 U+E007F |"); backspace(state, 0); state.assertEquals("'a' U+E0067 |"); backspace(state, 0); state.assertEquals("'a' |"); // Isolated tag chars state.setByString("'a' U+E0067 U+E0067 'b' |"); backspace(state, 0); state.assertEquals("'a' U+E0067 U+E0067 |"); backspace(state, 0); state.assertEquals("'a' U+E0067 |"); backspace(state, 0); state.assertEquals("'a' |"); // Isolated tab term. state.setByString("'a' U+E007F U+E007F 'b' |"); backspace(state, 0); state.assertEquals("'a' U+E007F U+E007F |"); backspace(state, 0); state.assertEquals("'a' U+E007F |"); backspace(state, 0); state.assertEquals("'a' |"); // Immediate tag_term after tag_base state.setByString("'a' U+1F3F4 U+E007F U+1F3F4 U+E007F 'b' |"); backspace(state, 0); state.assertEquals("'a' U+1F3F4 U+E007F U+1F3F4 U+E007F |"); backspace(state, 0); state.assertEquals("'a' U+1F3F4 U+E007F |"); backspace(state, 0); state.assertEquals("'a' |"); } @SmallTest Loading
core/tests/coretests/src/android/text/method/ForwardDeleteTest.java +44 −15 Original line number Diff line number Diff line Loading @@ -37,23 +37,12 @@ public class ForwardDeleteTest extends KeyListenerTestCase { // Sync the state to the TextView and call onKeyDown with KEYCODE_FORWARD_DEL key event. // Then update the state to the result of TextView. private void forwardDelete(final EditorState state, int modifiers) { mActivity.runOnUiThread(new Runnable() { public void run() { mTextView.setText(state.mText, BufferType.EDITABLE); mTextView.setKeyListener(mKeyListener); mTextView.setSelection(state.mSelectionStart, state.mSelectionEnd); } }); mInstrumentation.waitForIdleSync(); assertTrue(mTextView.hasWindowFocus()); final KeyEvent keyEvent = getKey(KeyEvent.KEYCODE_FORWARD_DEL, modifiers); mActivity.runOnUiThread(new Runnable() { public void run() { mTextView.onKeyDown(keyEvent.getKeyCode(), keyEvent); } }); mInstrumentation.waitForIdleSync(); state.mText = mTextView.getText(); state.mSelectionStart = mTextView.getSelectionStart(); Loading Loading @@ -186,6 +175,46 @@ public class ForwardDeleteTest extends KeyListenerTestCase { state.assertEquals("| U+1F1FA"); forwardDelete(state, 0); state.assertEquals("|"); // Incomplete sequence. (no tag_term:U+E007E) state.setByString("| 'a' U+1F3F4 U+E0067 'b'"); forwardDelete(state, 0); state.assertEquals("| U+1F3F4 U+E0067 'b'"); forwardDelete(state, 0); state.assertEquals("| 'b'"); // No tag_base state.setByString("| 'a' U+E0067 U+E007F 'b'"); forwardDelete(state, 0); state.assertEquals("| 'b'"); // Isolated tag chars state.setByString("| 'a' U+E0067 U+E0067 'b'"); forwardDelete(state, 0); state.assertEquals("| 'b'"); // Isolated tag base. state.setByString("| 'a' U+1F3F4 U+1F3F4 'b'"); forwardDelete(state, 0); state.assertEquals("| U+1F3F4 U+1F3F4 'b'"); forwardDelete(state, 0); state.assertEquals("| U+1F3F4 'b'"); forwardDelete(state, 0); state.assertEquals("| 'b'"); // Isolated tab term. state.setByString("| 'a' U+E007F U+E007F 'b'"); forwardDelete(state, 0); state.assertEquals("| 'b'"); // Immediate tag_term after tag_base state.setByString("| 'a' U+1F3F4 U+E007F U+1F3F4 U+E007F 'b'"); forwardDelete(state, 0); state.assertEquals("| U+1F3F4 U+E007F U+1F3F4 U+E007F 'b'"); forwardDelete(state, 0); state.assertEquals("| U+1F3F4 U+E007F 'b'"); forwardDelete(state, 0); state.assertEquals("| 'b'"); } @SmallTest Loading
core/tests/coretests/src/android/text/method/KeyListenerTestCase.java +3 −18 Original line number Diff line number Diff line Loading @@ -17,41 +17,26 @@ package android.text.method; import android.app.Instrumentation; import android.test.ActivityInstrumentationTestCase2; import android.text.format.DateUtils; import android.test.InstrumentationTestCase; import android.view.KeyEvent; import android.widget.EditText; import android.widget.TextViewActivity; import com.android.frameworks.coretests.R; public abstract class KeyListenerTestCase extends ActivityInstrumentationTestCase2<TextViewActivity> { public abstract class KeyListenerTestCase extends InstrumentationTestCase { protected TextViewActivity mActivity; protected Instrumentation mInstrumentation; protected EditText mTextView; public KeyListenerTestCase() { super(TextViewActivity.class); } @Override protected void setUp() throws Exception { super.setUp(); mActivity = getActivity(); mInstrumentation = getInstrumentation(); mTextView = (EditText) mActivity.findViewById(R.id.textview); mActivity.runOnUiThread(new Runnable() { public void run() { // Ensure that the screen is on for this test. mTextView.setKeepScreenOn(true); } }); assertTrue(mActivity.waitForWindowFocus(5 * DateUtils.SECOND_IN_MILLIS)); mTextView = new EditText(mInstrumentation.getContext()); } protected static KeyEvent getKey(int keycode, int metaState) { Loading