summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/text/AndroidBidi.java8
-rw-r--r--core/java/android/text/BidiFormatter.java12
-rw-r--r--core/java/android/text/Emoji.java51
-rw-r--r--core/tests/coretests/src/android/text/EmojiTest.java120
4 files changed, 164 insertions, 27 deletions
diff --git a/core/java/android/text/AndroidBidi.java b/core/java/android/text/AndroidBidi.java
index 179d545f8ccd..72383cf377e9 100644
--- a/core/java/android/text/AndroidBidi.java
+++ b/core/java/android/text/AndroidBidi.java
@@ -32,8 +32,12 @@ import com.android.internal.annotations.VisibleForTesting;
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public class AndroidBidi {
- private static class EmojiBidiOverride extends BidiClassifier {
- EmojiBidiOverride() {
+ /**
+ * Overrides ICU {@link BidiClassifier} in order to correctly handle character directions for
+ * newest emoji that ICU is not aware of.
+ */
+ public static class EmojiBidiOverride extends BidiClassifier {
+ public EmojiBidiOverride() {
super(null /* No persisting object needed */);
}
diff --git a/core/java/android/text/BidiFormatter.java b/core/java/android/text/BidiFormatter.java
index f65f39762b75..77f17a7d2377 100644
--- a/core/java/android/text/BidiFormatter.java
+++ b/core/java/android/text/BidiFormatter.java
@@ -21,6 +21,8 @@ import static android.text.TextDirectionHeuristics.FIRSTSTRONG_LTR;
import android.annotation.Nullable;
import android.view.View;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.Locale;
/**
@@ -570,8 +572,10 @@ public final class BidiFormatter {
/**
* An object that estimates the directionality of a given string by various methods.
*
+ * @hide
*/
- private static class DirectionalityEstimator {
+ @VisibleForTesting
+ public static class DirectionalityEstimator {
// Internal static variables and constants.
@@ -598,7 +602,11 @@ public final class BidiFormatter {
}
}
- private static byte getDirectionality(int codePoint) {
+ /**
+ * Return Character directionality. Same as {@link Character#getDirectionality(int)} except
+ * it overrides values for newest emoji that are not covered by ICU.
+ */
+ public static byte getDirectionality(int codePoint) {
if (Emoji.isNewEmoji(codePoint)) {
// TODO: Fix or remove once emoji-data.text 5.0 is in ICU or update to 6.0.
return Character.DIRECTIONALITY_OTHER_NEUTRALS;
diff --git a/core/java/android/text/Emoji.java b/core/java/android/text/Emoji.java
index d33aad99e1a5..876c64eebf84 100644
--- a/core/java/android/text/Emoji.java
+++ b/core/java/android/text/Emoji.java
@@ -46,44 +46,49 @@ public class Emoji {
return UCharacter.hasBinaryProperty(codePoint, UProperty.EMOJI_MODIFIER);
}
- // Returns true if the given code point is emoji modifier base.
- public static boolean isEmojiModifierBase(int codePoint) {
+ //
+
+ /**
+ * Returns true if the given code point is emoji modifier base.
+ * @param c codepoint to check
+ * @return true if is emoji modifier base
+ */
+ public static boolean isEmojiModifierBase(int c) {
// These two characters were removed from Emoji_Modifier_Base in Emoji 4.0, but we need to
// keep them as emoji modifier bases since there are fonts and user-generated text out there
// that treats these as potential emoji bases.
- if (codePoint == 0x1F91D || codePoint == 0x1F93C) {
+ if (c == 0x1F91D || c == 0x1F93C) {
return true;
}
- // Emoji Modifier Base characters new in Unicode emoji 5.0.
- // From http://www.unicode.org/Public/emoji/5.0/emoji-data.txt
- // TODO: Remove once emoji-data.text 5.0 is in ICU or update to 6.0.
- if (codePoint == 0x1F91F
- || (0x1F931 <= codePoint && codePoint <= 0x1F932)
- || (0x1F9D1 <= codePoint && codePoint <= 0x1F9DD)) {
+ // Emoji Modifier Base characters new in Unicode emoji 11
+ // From https://www.unicode.org/Public/emoji/11.0/emoji-data.txt
+ // TODO: Remove once emoji-data.text 11 is in ICU or update to 11.
+ if ((0x1F9B5 <= c && c <= 0x1F9B6) || (0x1F9B8 <= c && c <= 0x1F9B9)) {
return true;
}
- return UCharacter.hasBinaryProperty(codePoint, UProperty.EMOJI_MODIFIER_BASE);
+ return UCharacter.hasBinaryProperty(c, UProperty.EMOJI_MODIFIER_BASE);
}
/**
* Returns true if the character is a new emoji still not supported in our version of ICU.
*/
- public static boolean isNewEmoji(int codePoint) {
- // Emoji characters new in Unicode emoji 5.0.
- // From http://www.unicode.org/Public/emoji/5.0/emoji-data.txt
- // TODO: Remove once emoji-data.text 5.0 is in ICU or update to 6.0.
- if (codePoint < 0x1F6F7 || codePoint > 0x1F9E6) {
+ public static boolean isNewEmoji(int c) {
+ // Emoji characters new in Unicode emoji 11
+ // From https://www.unicode.org/Public/emoji/11.0/emoji-data.txt
+ // TODO: Remove once emoji-data.text 11 is in ICU or update to 11.
+ if (c < 0x1F6F9 || c > 0x1F9FF) {
// Optimization for characters outside the new emoji range.
return false;
}
- return (0x1F6F7 <= codePoint && codePoint <= 0x1F6F8)
- || codePoint == 0x1F91F
- || (0x1F928 <= codePoint && codePoint <= 0x1F92F)
- || (0x1F931 <= codePoint && codePoint <= 0x1F932)
- || codePoint == 0x1F94C
- || (0x1F95F <= codePoint && codePoint <= 0x1F96B)
- || (0x1F992 <= codePoint && codePoint <= 0x1F997)
- || (0x1F9D0 <= codePoint && codePoint <= 0x1F9E6);
+ return c == 0x265F || c == 0x267E || c == 0x1F6F9 || c == 0x1F97A
+ || (0x1F94D <= c && c <= 0x1F94F)
+ || (0x1F96C <= c && c <= 0x1F970)
+ || (0x1F973 <= c && c <= 0x1F976)
+ || (0x1F97C <= c && c <= 0x1F97F)
+ || (0x1F998 <= c && c <= 0x1F9A2)
+ || (0x1F9B0 <= c && c <= 0x1F9B9)
+ || (0x1F9C1 <= c && c <= 0x1F9C2)
+ || (0x1F9E7 <= c && c <= 0x1F9FF);
}
/**
diff --git a/core/tests/coretests/src/android/text/EmojiTest.java b/core/tests/coretests/src/android/text/EmojiTest.java
new file mode 100644
index 000000000000..313f1b6a6614
--- /dev/null
+++ b/core/tests/coretests/src/android/text/EmojiTest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.icu.lang.UCharacterDirection;
+import android.icu.text.Bidi;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Emoji and ICU drops does not happen at the same time. Therefore there are almost always cases
+ * where the existing ICU version is not aware of the latest emoji that Android supports.
+ * This test covers Emoji and ICU related functions where other components such as
+ * {@link AndroidBidi}, {@link BidiFormatter} depend on. The tests are collected into the same
+ * class since the changes effect all those classes.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class EmojiTest {
+
+ @Test
+ public void testIsNewEmoji_Emoji5() {
+ // each row in the data is the range of emoji
+ final int[][][] data = new int[][][]{
+ { // EMOJI 5
+ // range of emoji: i.e from 0x1F6F7 to 0x1F6F8 inclusive
+ {0x1F6F7, 0x1F6F8},
+ {0x1F91F, 0x1F91F},
+ {0x1F928, 0x1F92F},
+ {0x1F94C, 0x1F94C},
+ {0x1F95F, 0x1F96B},
+ {0x1F992, 0x1F997},
+ {0x1F9D0, 0x1F9E6},
+ },
+ { // EMOJI 11
+ {0x265F, 0x265F},
+ {0x267E, 0x267E},
+ {0x1F6F9, 0x1F6F9},
+ {0x1F94D, 0x1F94F},
+ {0x1F96C, 0x1F970},
+ {0x1F973, 0x1F976},
+ {0x1F97A, 0x1F97A},
+ {0x1F97C, 0x1F97F},
+ {0x1F998, 0x1F9A2},
+ {0x1F9B0, 0x1F9B9},
+ {0x1F9C1, 0x1F9C2},
+ {0x1F9E7, 0x1F9FF},
+ }
+ };
+
+ final Bidi icuBidi = new Bidi(0 /* maxLength */, 0 /* maxRunCount */);
+ icuBidi.setCustomClassifier(new AndroidBidi.EmojiBidiOverride());
+
+ for (int version = 0; version < data.length; version++) {
+ for (int row = 0; row < data[version].length; row++) {
+ for (int c = data[version][row][0]; c < data[version][row][1]; c++) {
+ assertTrue(Integer.toHexString(c) + " should be emoji", Emoji.isEmoji(c));
+
+ assertEquals(Integer.toHexString(c) + " should have neutral directionality",
+ Character.DIRECTIONALITY_OTHER_NEUTRALS,
+ BidiFormatter.DirectionalityEstimator.getDirectionality(c));
+
+ assertEquals(Integer.toHexString(c) + " shoud be OTHER_NEUTRAL for ICU Bidi",
+ UCharacterDirection.OTHER_NEUTRAL, icuBidi.getCustomizedClass(c));
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testisEmojiModifierBase_LegacyCompat() {
+ assertTrue(Emoji.isEmojiModifierBase(0x1F91D));
+ assertTrue(Emoji.isEmojiModifierBase(0x1F93C));
+ }
+
+ @Test
+ public void testisEmojiModifierBase() {
+ // each row in the data is the range of emoji
+ final int[][][] data = new int[][][]{
+ { // EMOJI 5
+ // range of emoji: i.e from 0x1F91F to 0x1F91F inclusive
+ {0x1F91F, 0x1F91F},
+ {0x1F931, 0x1F932},
+ {0x1F9D1, 0x1F9DD},
+ },
+ { // EMOJI 11
+ {0x1F9B5, 0x1F9B6},
+ {0x1F9B8, 0x1F9B9}
+ }
+ };
+ for (int version = 0; version < data.length; version++) {
+ for (int row = 0; row < data[version].length; row++) {
+ for (int c = data[version][row][0]; c < data[version][row][1]; c++) {
+ assertTrue(Integer.toHexString(c) + " should be emoji modifier base",
+ Emoji.isEmojiModifierBase(c));
+ }
+ }
+ }
+ }
+}