Move hyphenator mapping to native code.

This CL contains the following changes:
- Replace Hyphenator mapping in Java with minikin::HyphenatorMap.
- Remove locale tracking code from StaticLayout.java
- Stop creating Hyphenator pointer array in StaticLayout.

Bug: 65024629
Bug: 67319341
Test: bit CtsTextTestCases:*
Test: bit FrameworksCoreTests:android.text.
Test: bit FrameworksCoreTests:android.widget.TextViewTest
Change-Id: Ib88c8e816c70959057abe296cc434ddb70bc397a
diff --git a/core/java/android/text/Hyphenator.java b/core/java/android/text/Hyphenator.java
index ddfc00c..4f1488e 100644
--- a/core/java/android/text/Hyphenator.java
+++ b/core/java/android/text/Hyphenator.java
@@ -16,262 +16,15 @@
 
 package android.text;
 
-import android.annotation.IntRange;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.OsConstants;
-import android.util.Log;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.util.HashMap;
-import java.util.Locale;
-
 /**
- * Hyphenator is a wrapper class for a native implementation of automatic hyphenation,
+ * Hyphenator just initializes the native implementation of automatic hyphenation,
  * in essence finding valid hyphenation opportunities in a word.
  *
  * @hide
  */
 public class Hyphenator {
-    private static String TAG = "Hyphenator";
-
-    private final static Object sLock = new Object();
-
-    @GuardedBy("sLock")
-    final static HashMap<Locale, Hyphenator> sMap = new HashMap<Locale, Hyphenator>();
-
-    private final long mNativePtr;
-    private final HyphenationData mData;
-
-    private Hyphenator(long nativePtr, HyphenationData data) {
-        mNativePtr = nativePtr;
-        mData = data;
-    }
-
-    public long getNativePtr() {
-        return mNativePtr;
-    }
-
-    public static Hyphenator get(@Nullable Locale locale) {
-        synchronized (sLock) {
-            Hyphenator result = sMap.get(locale);
-            if (result != null) {
-                return result;
-            }
-
-            // If there's a variant, fall back to language+variant only, if available
-            final String variant = locale.getVariant();
-            if (!variant.isEmpty()) {
-                final Locale languageAndVariantOnlyLocale =
-                        new Locale(locale.getLanguage(), "", variant);
-                result = sMap.get(languageAndVariantOnlyLocale);
-                if (result != null) {
-                    return putAlias(locale, result);
-                }
-            }
-
-            // Fall back to language-only, if available
-            final Locale languageOnlyLocale = new Locale(locale.getLanguage());
-            result = sMap.get(languageOnlyLocale);
-            if (result != null) {
-                return putAlias(locale, result);
-            }
-
-            // Fall back to script-only, if available
-            final String script = locale.getScript();
-            if (!script.equals("")) {
-                final Locale scriptOnlyLocale = new Locale.Builder()
-                        .setLanguage("und")
-                        .setScript(script)
-                        .build();
-                result = sMap.get(scriptOnlyLocale);
-                if (result != null) {
-                    return putAlias(locale, result);
-                }
-            }
-
-            return putEmptyAlias(locale);
-        }
-    }
-
-    private static class HyphenationData {
-        private static final String SYSTEM_HYPHENATOR_LOCATION = "/system/usr/hyphen-data";
-
-        public final int mMinPrefix, mMinSuffix;
-        public final long mDataAddress;
-
-        // Reasonable enough values for cases where we have no hyphenation patterns but may be able
-        // to do some automatic hyphenation based on characters. These values would be used very
-        // rarely.
-        private static final int DEFAULT_MIN_PREFIX = 2;
-        private static final int DEFAULT_MIN_SUFFIX = 2;
-
-        public static final HyphenationData sEmptyData =
-                new HyphenationData(DEFAULT_MIN_PREFIX, DEFAULT_MIN_SUFFIX);
-
-        // Create empty HyphenationData.
-        private HyphenationData(int minPrefix, int minSuffix) {
-            mMinPrefix = minPrefix;
-            mMinSuffix = minSuffix;
-            mDataAddress = 0;
-        }
-
-        HyphenationData(String languageTag, int minPrefix, int minSuffix) {
-            mMinPrefix = minPrefix;
-            mMinSuffix = minSuffix;
-
-            final String patternFilename = "hyph-" + languageTag.toLowerCase(Locale.US) + ".hyb";
-            final File patternFile = new File(SYSTEM_HYPHENATOR_LOCATION, patternFilename);
-            if (!patternFile.canRead()) {
-                mDataAddress = 0;
-            } else {
-                long address;
-                try (RandomAccessFile f = new RandomAccessFile(patternFile, "r")) {
-                    address = Os.mmap(0, f.length(), OsConstants.PROT_READ,
-                            OsConstants.MAP_SHARED, f.getFD(), 0 /* offset */);
-                } catch (IOException | ErrnoException e) {
-                    Log.e(TAG, "error loading hyphenation " + patternFile, e);
-                    address = 0;
-                }
-                mDataAddress = address;
-            }
-        }
-    }
-
-    // Do not call this method outside of init method.
-    private static Hyphenator putNewHyphenator(Locale loc, HyphenationData data) {
-        final Hyphenator hyphenator = new Hyphenator(nBuildHyphenator(
-                data.mDataAddress, loc.getLanguage(), data.mMinPrefix, data.mMinSuffix), data);
-        sMap.put(loc, hyphenator);
-        return hyphenator;
-    }
-
-    // Do not call this method outside of init method.
-    private static void loadData(String langTag, int minPrefix, int maxPrefix) {
-        final HyphenationData data = new HyphenationData(langTag, minPrefix, maxPrefix);
-        putNewHyphenator(Locale.forLanguageTag(langTag), data);
-    }
-
-    // Caller must acquire sLock before calling this method.
-    // The Hyphenator for the baseLangTag must exists.
-    private static Hyphenator addAliasByTag(String langTag, String baseLangTag) {
-        return putAlias(Locale.forLanguageTag(langTag),
-                sMap.get(Locale.forLanguageTag(baseLangTag)));
-    }
-
-    // Caller must acquire sLock before calling this method.
-    private static Hyphenator putAlias(Locale locale, Hyphenator base) {
-        return putNewHyphenator(locale, base.mData);
-    }
-
-    // Caller must acquire sLock before calling this method.
-    private static Hyphenator putEmptyAlias(Locale locale) {
-        return putNewHyphenator(locale, HyphenationData.sEmptyData);
-    }
-
-    // TODO: Confirm that these are the best values. Various sources suggest (1, 1), but
-    // that appears too small.
-    private static final int INDIC_MIN_PREFIX = 2;
-    private static final int INDIC_MIN_SUFFIX = 2;
-
-    /**
-     * Load hyphenation patterns at initialization time. We want to have patterns
-     * for all locales loaded and ready to use so we don't have to do any file IO
-     * on the UI thread when drawing text in different locales.
-     *
-     * @hide
-     */
     public static void init() {
-        synchronized (sLock) {
-            sMap.put(null, null);
-
-            loadData("as", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Assamese
-            loadData("bg", 2, 2); // Bulgarian
-            loadData("bn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Bengali
-            loadData("cu", 1, 2); // Church Slavonic
-            loadData("cy", 2, 3); // Welsh
-            loadData("da", 2, 2); // Danish
-            loadData("de-1901", 2, 2); // German 1901 orthography
-            loadData("de-1996", 2, 2); // German 1996 orthography
-            loadData("de-CH-1901", 2, 2); // Swiss High German 1901 orthography
-            loadData("en-GB", 2, 3); // British English
-            loadData("en-US", 2, 3); // American English
-            loadData("es", 2, 2); // Spanish
-            loadData("et", 2, 3); // Estonian
-            loadData("eu", 2, 2); // Basque
-            loadData("fr", 2, 3); // French
-            loadData("ga", 2, 3); // Irish
-            loadData("gu", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Gujarati
-            loadData("hi", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Hindi
-            loadData("hr", 2, 2); // Croatian
-            loadData("hu", 2, 2); // Hungarian
-            // texhyphen sources say Armenian may be (1, 2); but that it needs confirmation.
-            // Going with a more conservative value of (2, 2) for now.
-            loadData("hy", 2, 2); // Armenian
-            loadData("kn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Kannada
-            loadData("ml", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Malayalam
-            loadData("mn-Cyrl", 2, 2); // Mongolian in Cyrillic script
-            loadData("mr", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Marathi
-            loadData("nb", 2, 2); // Norwegian Bokmål
-            loadData("nn", 2, 2); // Norwegian Nynorsk
-            loadData("or", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Oriya
-            loadData("pa", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Punjabi
-            loadData("pt", 2, 3); // Portuguese
-            loadData("sl", 2, 2); // Slovenian
-            loadData("ta", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Tamil
-            loadData("te", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Telugu
-            loadData("tk", 2, 2); // Turkmen
-            loadData("und-Ethi", 1, 1); // Any language in Ethiopic script
-
-            // Following two hyphenators do not have pattern files but there is some special logic
-            // based on language.
-            loadData("ca", 2, 2);  // Catalan
-            loadData("pl", 2, 2);  // Polish
-
-            // English locales that fall back to en-US. The data is
-            // from CLDR. It's all English locales, minus the locales whose
-            // parent is en-001 (from supplementalData.xml, under <parentLocales>).
-            // TODO: Figure out how to get this from ICU.
-            addAliasByTag("en-AS", "en-US"); // English (American Samoa)
-            addAliasByTag("en-GU", "en-US"); // English (Guam)
-            addAliasByTag("en-MH", "en-US"); // English (Marshall Islands)
-            addAliasByTag("en-MP", "en-US"); // English (Northern Mariana Islands)
-            addAliasByTag("en-PR", "en-US"); // English (Puerto Rico)
-            addAliasByTag("en-UM", "en-US"); // English (United States Minor Outlying Islands)
-            addAliasByTag("en-VI", "en-US"); // English (Virgin Islands)
-
-            // All English locales other than those falling back to en-US are mapped to en-GB.
-            addAliasByTag("en", "en-GB");
-
-            // For German, we're assuming the 1996 (and later) orthography by default.
-            addAliasByTag("de", "de-1996");
-            // Liechtenstein uses the Swiss hyphenation rules for the 1901 orthography.
-            addAliasByTag("de-LI-1901", "de-CH-1901");
-
-            // Norwegian is very probably Norwegian Bokmål.
-            addAliasByTag("no", "nb");
-
-            // Use mn-Cyrl. According to CLDR's likelySubtags.xml, mn is most likely to be mn-Cyrl.
-            addAliasByTag("mn", "mn-Cyrl"); // Mongolian
-
-            // Fall back to Ethiopic script for languages likely to be written in Ethiopic.
-            // Data is from CLDR's likelySubtags.xml.
-            // TODO: Convert this to a mechanism using ICU4J's ULocale#addLikelySubtags().
-            addAliasByTag("am", "und-Ethi"); // Amharic
-            addAliasByTag("byn", "und-Ethi"); // Blin
-            addAliasByTag("gez", "und-Ethi"); // Geʻez
-            addAliasByTag("ti", "und-Ethi"); // Tigrinya
-            addAliasByTag("wal", "und-Ethi"); // Wolaytta
-        }
-    };
-
-    private static native long nBuildHyphenator(long dataAddress,
-            @NonNull String langTag, @IntRange(from = 1) int minPrefix,
-            @IntRange(from = 1) int minSuffix);
+        nInit();
+    }
+    private static native void nInit();
 }
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 4b6b6ae..5c60188 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -21,21 +21,18 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.Paint;
-import android.os.LocaleList;
 import android.text.style.LeadingMarginSpan;
 import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
 import android.text.style.LineHeightSpan;
 import android.text.style.MetricAffectingSpan;
 import android.text.style.TabStopSpan;
 import android.util.Log;
-import android.util.Pair;
 import android.util.Pools.SynchronizedPool;
 
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.GrowingArrayUtils;
 
 import java.util.Arrays;
-import java.util.Locale;
 
 /**
  * StaticLayout is a Layout for text that will not be edited after it
@@ -101,7 +98,6 @@
             b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
             b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
             b.mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
-            b.mLocales = null;
 
             b.mMeasuredText = MeasuredText.obtain();
             return b;
@@ -118,7 +114,6 @@
             b.mMeasuredText = null;
             b.mLeftIndents = null;
             b.mRightIndents = null;
-            b.mLocales = null;
             b.mLeftPaddings = null;
             b.mRightPaddings = null;
             nFinishBuilder(b.mNativePtr);
@@ -409,17 +404,6 @@
             return this;
         }
 
-        @NonNull
-        private long[] getHyphenators(@NonNull LocaleList locales) {
-            final int length = locales.size();
-            final long[] result = new long[length];
-            for (int i = 0; i < length; i++) {
-                final Locale locale = locales.get(i);
-                result[i] = Hyphenator.get(locale).getNativePtr();
-            }
-            return result;
-        }
-
         /**
          * Measurement and break iteration is done in native code. The protocol for using
          * the native code is as follows.
@@ -438,27 +422,12 @@
          * After all paragraphs, call finish() to release expensive buffers.
          */
 
-        private Pair<String, long[]> getLocaleAndHyphenatorIfChanged(TextPaint paint) {
-            final LocaleList locales = paint.getTextLocales();
-            if (!locales.equals(mLocales)) {
-                mLocales = locales;
-                return new Pair(locales.toLanguageTags(), getHyphenators(locales));
-            } else {
-                // passing null means keep current locale.
-                // TODO: move locale change detection to native.
-                return new Pair(null, null);
-            }
-        }
-
         /* package */ void addStyleRun(TextPaint paint, int start, int end, boolean isRtl) {
-            Pair<String, long[]> locHyph = getLocaleAndHyphenatorIfChanged(paint);
-            nAddStyleRun(mNativePtr, paint.getNativeInstance(), start, end, isRtl, locHyph.first,
-                    locHyph.second);
+            nAddStyleRun(mNativePtr, paint.getNativeInstance(), start, end, isRtl);
         }
 
         /* package */ void addReplacementRun(TextPaint paint, int start, int end, float width) {
-            Pair<String, long[]> locHyph = getLocaleAndHyphenatorIfChanged(paint);
-            nAddReplacementRun(mNativePtr, start, end, width, locHyph.first, locHyph.second);
+            nAddReplacementRun(mNativePtr, paint.getNativeInstance(), start, end, width);
         }
 
         /**
@@ -516,8 +485,6 @@
         // This will go away and be subsumed by native builder code
         private MeasuredText mMeasuredText;
 
-        private LocaleList mLocales;
-
         private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<>(3);
     }
 
@@ -807,9 +774,6 @@
                 }
             }
 
-            // TODO: Move locale tracking code to native.
-            b.mLocales = null;  // Reset the locale tracking.
-
             nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart,
                     firstWidth, firstWidthLineCount, restWidth,
                     variableTabStops, TAB_INCREMENT, b.mBreakStrategy, b.mHyphenationFrequency,
@@ -1537,15 +1501,16 @@
             @Nullable int[] indents, @Nullable int[] leftPaddings, @Nullable int[] rightPaddings,
             @IntRange(from = 0) int indentsOffset);
 
+    // TODO: Make this method CriticalNative once native code defers doing layouts.
     private static native void nAddStyleRun(
             /* non-zero */ long nativePtr, /* non-zero */ long nativePaint,
-            @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl,
-            @Nullable String languageTags, @Nullable long[] hyphenators);
+            @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl);
 
-    private static native void nAddReplacementRun(/* non-zero */ long nativePtr,
+    // TODO: Make this method CriticalNative once native code defers doing layouts.
+    private static native void nAddReplacementRun(
+            /* non-zero */ long nativePtr, /* non-zero */ long nativePaint,
             @IntRange(from = 0) int start, @IntRange(from = 0) int end,
-            @FloatRange(from = 0.0f) float width, @Nullable String languageTags,
-            @Nullable long[] hyphenators);
+            @FloatRange(from = 0.0f) float width);
 
     // populates LineBreaks and returns the number of breaks found
     //
diff --git a/core/jni/android_text_Hyphenator.cpp b/core/jni/android_text_Hyphenator.cpp
index da025da..b46f389 100644
--- a/core/jni/android_text_Hyphenator.cpp
+++ b/core/jni/android_text_Hyphenator.cpp
@@ -14,24 +14,155 @@
  * limitations under the License.
  */
 
-#include <cstdint>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <algorithm>
+
 #include <core_jni_helpers.h>
 #include <minikin/Hyphenator.h>
-#include <nativehelper/ScopedUtfChars.h>
 
 namespace android {
 
-static jlong nBuildHyphenator(JNIEnv* env, jclass, jlong dataAddress, jstring lang,
-        jint minPrefix, jint minSuffix) {
-    const uint8_t* bytebuf = reinterpret_cast<const uint8_t*>(dataAddress);  // null allowed.
-    ScopedUtfChars language(env, lang);
-    minikin::Hyphenator* hyphenator = minikin::Hyphenator::loadBinary(
-            bytebuf, minPrefix, minSuffix, language.c_str(), language.size());
-    return reinterpret_cast<jlong>(hyphenator);
+static std::string buildFileName(const std::string& locale) {
+    constexpr char SYSTEM_HYPHENATOR_PREFIX[] = "/system/usr/hyphen-data/hyph-";
+    constexpr char SYSTEM_HYPHENATOR_SUFFIX[] = ".hyb";
+    std::string lowerLocale;
+    lowerLocale.reserve(locale.size());
+    std::transform(locale.begin(), locale.end(), std::back_inserter(lowerLocale), ::tolower);
+    return SYSTEM_HYPHENATOR_PREFIX + lowerLocale + SYSTEM_HYPHENATOR_SUFFIX;
+}
+
+static const uint8_t* mmapPatternFile(const std::string& locale) {
+    const std::string hyFilePath = buildFileName(locale);
+    const int fd = open(hyFilePath.c_str(), O_RDONLY);
+    if (fd == -1) {
+        return nullptr;  // Open failed.
+    }
+
+    struct stat st = {};
+    if (fstat(fd, &st) == -1) {  // Unlikely to happen.
+        close(fd);
+        return nullptr;
+    }
+
+    void* ptr = mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd, 0 /* offset */);
+    close(fd);
+    if (ptr == MAP_FAILED) {
+        return nullptr;
+    }
+    return reinterpret_cast<const uint8_t*>(ptr);
+}
+
+static void addHyphenatorWithoutPatternFile(const std::string& locale, int minPrefix,
+        int minSuffix) {
+    minikin::addHyphenator(locale, minikin::Hyphenator::loadBinary(
+            nullptr, minPrefix, minSuffix, locale));
+}
+
+static void addHyphenator(const std::string& locale, int minPrefix, int minSuffix) {
+    const uint8_t* ptr = mmapPatternFile(locale);
+    if (ptr == nullptr) {
+        ALOGE("Unable to find pattern file or unable to map it for %s", locale.c_str());
+        return;
+    }
+    minikin::addHyphenator(locale, minikin::Hyphenator::loadBinary(
+            ptr, minPrefix, minSuffix, locale));
+}
+
+static void addHyphenatorAlias(const std::string& from, const std::string& to) {
+    minikin::addHyphenatorAlias(from, to);
+}
+
+static void init() {
+    // TODO: Confirm that these are the best values. Various sources suggest (1, 1), but that
+    // appears too small.
+    constexpr int INDIC_MIN_PREFIX = 2;
+    constexpr int INDIC_MIN_SUFFIX = 2;
+
+    addHyphenator("as", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Assamese
+    addHyphenator("bg", 2, 2); // Bulgarian
+    addHyphenator("bn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Bengali
+    addHyphenator("cu", 1, 2); // Church Slavonic
+    addHyphenator("cy", 2, 3); // Welsh
+    addHyphenator("da", 2, 2); // Danish
+    addHyphenator("de-1901", 2, 2); // German 1901 orthography
+    addHyphenator("de-1996", 2, 2); // German 1996 orthography
+    addHyphenator("de-CH-1901", 2, 2); // Swiss High German 1901 orthography
+    addHyphenator("en-GB", 2, 3); // British English
+    addHyphenator("en-US", 2, 3); // American English
+    addHyphenator("es", 2, 2); // Spanish
+    addHyphenator("et", 2, 3); // Estonian
+    addHyphenator("eu", 2, 2); // Basque
+    addHyphenator("fr", 2, 3); // French
+    addHyphenator("ga", 2, 3); // Irish
+    addHyphenator("gu", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Gujarati
+    addHyphenator("hi", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Hindi
+    addHyphenator("hr", 2, 2); // Croatian
+    addHyphenator("hu", 2, 2); // Hungarian
+    // texhyphen sources say Armenian may be (1, 2); but that it needs confirmation.
+    // Going with a more conservative value of (2, 2) for now.
+    addHyphenator("hy", 2, 2); // Armenian
+    addHyphenator("kn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Kannada
+    addHyphenator("ml", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Malayalam
+    addHyphenator("mn-Cyrl", 2, 2); // Mongolian in Cyrillic script
+    addHyphenator("mr", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Marathi
+    addHyphenator("nb", 2, 2); // Norwegian Bokmål
+    addHyphenator("nn", 2, 2); // Norwegian Nynorsk
+    addHyphenator("or", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Oriya
+    addHyphenator("pa", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Punjabi
+    addHyphenator("pt", 2, 3); // Portuguese
+    addHyphenator("sl", 2, 2); // Slovenian
+    addHyphenator("ta", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Tamil
+    addHyphenator("te", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Telugu
+    addHyphenator("tk", 2, 2); // Turkmen
+    addHyphenator("und-Ethi", 1, 1); // Any language in Ethiopic script
+
+    // Following two hyphenators do not have pattern files but there is some special logic based on
+    // language.
+    addHyphenatorWithoutPatternFile("ca", 2, 2);  // Catalan
+    addHyphenatorWithoutPatternFile("pl", 2, 2);  // Polish
+
+    // English locales that fall back to en-US. The data is from CLDR. It's all English locales,
+    // minus the locales whose parent is en-001 (from supplementalData.xml, under <parentLocales>).
+    // TODO: Figure out how to get this from ICU.
+    addHyphenatorAlias("en-AS", "en-US"); // English (American Samoa)
+    addHyphenatorAlias("en-GU", "en-US"); // English (Guam)
+    addHyphenatorAlias("en-MH", "en-US"); // English (Marshall Islands)
+    addHyphenatorAlias("en-MP", "en-US"); // English (Northern Mariana Islands)
+    addHyphenatorAlias("en-PR", "en-US"); // English (Puerto Rico)
+    addHyphenatorAlias("en-UM", "en-US"); // English (United States Minor Outlying Islands)
+    addHyphenatorAlias("en-VI", "en-US"); // English (Virgin Islands)
+
+    // All English locales other than those falling back to en-US are mapped to en-GB.
+    addHyphenatorAlias("en", "en-GB");
+
+    // For German, we're assuming the 1996 (and later) orthography by default.
+    addHyphenatorAlias("de", "de-1996");
+    // Liechtenstein uses the Swiss hyphenation rules for the 1901 orthography.
+    addHyphenatorAlias("de-LI-1901", "de-CH-1901");
+
+    // Norwegian is very probably Norwegian Bokmål.
+    addHyphenatorAlias("no", "nb");
+
+    // Use mn-Cyrl. According to CLDR's likelySubtags.xml, mn is most likely to be mn-Cyrl.
+    addHyphenatorAlias("mn", "mn-Cyrl"); // Mongolian
+
+    // Fall back to Ethiopic script for languages likely to be written in Ethiopic.
+    // Data is from CLDR's likelySubtags.xml.
+    // TODO: Convert this to a mechanism using ICU4J's ULocale#addLikelySubtags().
+    addHyphenatorAlias("am", "und-Ethi"); // Amharic
+    addHyphenatorAlias("byn", "und-Ethi"); // Blin
+    addHyphenatorAlias("gez", "und-Ethi"); // Geʻez
+    addHyphenatorAlias("ti", "und-Ethi"); // Tigrinya
+    addHyphenatorAlias("wal", "und-Ethi"); // Wolaytta
+
 }
 
 static const JNINativeMethod gMethods[] = {
-    {"nBuildHyphenator", "(JLjava/lang/String;II)J", (void*) nBuildHyphenator},
+    {"nInit", "()V", (void*) init},
 };
 
 int register_android_text_Hyphenator(JNIEnv* env) {
diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp
index 1f7277a..04e9dfd 100644
--- a/core/jni/android_text_StaticLayout.cpp
+++ b/core/jni/android_text_StaticLayout.cpp
@@ -195,49 +195,9 @@
     b->finish();
 }
 
-class ScopedNullableUtfString {
-public:
-    ScopedNullableUtfString(JNIEnv* env, jstring s) : mEnv(env), mStr(s) {
-        if (s == nullptr) {
-            mUtf8Chars = nullptr;
-        } else {
-            mUtf8Chars = mEnv->GetStringUTFChars(s, nullptr);
-        }
-    }
-
-    ~ScopedNullableUtfString() {
-        if (mUtf8Chars != nullptr) {
-            mEnv->ReleaseStringUTFChars(mStr, mUtf8Chars);
-        }
-    }
-
-    const char* get() const {
-        return mUtf8Chars;
-    }
-
-private:
-    JNIEnv* mEnv;
-    jstring mStr;
-    const char* mUtf8Chars;
-};
-
-static std::vector<minikin::Hyphenator*> makeHyphenators(JNIEnv* env, jlongArray hyphenators) {
-    std::vector<minikin::Hyphenator*> out;
-    if (hyphenators == nullptr) {
-        return out;
-    }
-    ScopedLongArrayRO longArray(env, hyphenators);
-    size_t size = longArray.size();
-    out.reserve(size);
-    for (size_t i = 0; i < size; i++) {
-        out.push_back(reinterpret_cast<minikin::Hyphenator*>(longArray[i]));
-    }
-    return out;
-}
-
 // Basically similar to Paint.getTextRunAdvances but with C++ interface
 static void nAddStyleRun(JNIEnv* env, jclass, jlong nativePtr, jlong nativePaint, jint start,
-        jint end, jboolean isRtl, jstring langTags, jlongArray hyphenators) {
+        jint end, jboolean isRtl) {
     minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr);
     Paint* paint = reinterpret_cast<Paint*>(nativePaint);
     const Typeface* typeface = paint->getAndroidTypeface();
@@ -246,16 +206,14 @@
     minikin::FontStyle style = MinikinUtils::prepareMinikinPaint(&minikinPaint, paint,
             typeface);
 
-    ScopedNullableUtfString langTagsString(env, langTags);
-    b->addStyleRun(&minikinPaint, resolvedTypeface->fFontCollection, style, start,
-            end, isRtl, langTagsString.get(), makeHyphenators(env, hyphenators));
+    b->addStyleRun(&minikinPaint, resolvedTypeface->fFontCollection, style, start, end, isRtl);
 }
 
-static void nAddReplacementRun(JNIEnv* env, jclass, jlong nativePtr,
-        jint start, jint end, jfloat width, jstring langTags, jlongArray hyphenators) {
+static void nAddReplacementRun(JNIEnv* env, jclass, jlong nativePtr, jlong nativePaint,
+        jint start, jint end, jfloat width) {
     minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr);
-    ScopedNullableUtfString langTagsString(env, langTags);
-    b->addReplacement(start, end, width, langTagsString.get(), makeHyphenators(env, hyphenators));
+    Paint* paint = reinterpret_cast<Paint*>(nativePaint);
+    b->addReplacement(start, end, width, paint->getMinikinLangListId());
 }
 
 static const JNINativeMethod gMethods[] = {
@@ -264,8 +222,8 @@
     {"nFreeBuilder", "(J)V", (void*) nFreeBuilder},
     {"nFinishBuilder", "(J)V", (void*) nFinishBuilder},
     {"nSetupParagraph", "(J[CIFIF[IIIIZ[I[I[II)V", (void*) nSetupParagraph},
-    {"nAddStyleRun", "(JJIIZLjava/lang/String;[J)V", (void*) nAddStyleRun},
-    {"nAddReplacementRun", "(JIIFLjava/lang/String;[J)V", (void*) nAddReplacementRun},
+    {"nAddStyleRun", "(JJIIZ)V", (void*) nAddStyleRun},
+    {"nAddReplacementRun", "(JJIIF)V", (void*) nAddReplacementRun},
     {"nComputeLineBreaks", "(JLandroid/text/StaticLayout$LineBreaks;[I[F[F[F[II[F)I",
         (void*) nComputeLineBreaks}
 };