Drop codepoints that are outside the Unicode range - DO NOT MERGE am: 0e441db0f7  -s ours am: 437445d1b7 am: c25ec29dfc
am: 55d1d9cb97

Change-Id: Ib0df8ed1a84a0e313b1147f42030db95db02668b
diff --git a/include/minikin/LineBreaker.h b/include/minikin/LineBreaker.h
index c91c0b3..d7a20e9 100644
--- a/include/minikin/LineBreaker.h
+++ b/include/minikin/LineBreaker.h
@@ -22,11 +22,14 @@
 #ifndef MINIKIN_LINE_BREAKER_H
 #define MINIKIN_LINE_BREAKER_H
 
+#include <gtest/gtest_prod.h>
 #include "unicode/brkiter.h"
 #include "unicode/locid.h"
 #include <cmath>
 #include <vector>
+#include "minikin/FontCollection.h"
 #include "minikin/Hyphenator.h"
+#include "minikin/MinikinFont.h"
 #include "minikin/WordBreaker.h"
 
 namespace minikin {
@@ -113,7 +116,7 @@
         // That logic could be here but it's better for performance that it's upstream because of
         // the cost of constructing and comparing the ICU Locale object.
         // Note: caller is responsible for managing lifetime of hyphenator
-        void setLocale(const icu::Locale& locale, Hyphenator* hyphenator);
+        void setLocales(const char* locales, const std::vector<Hyphenator*>& hyphenators);
 
         void resize(size_t size) {
             mTextBuf.resize(size);
@@ -252,6 +255,8 @@
         uint32_t mLastHyphenation;  // hyphen edit of last break kept for next line
         int mFirstTabIndex;
         size_t mSpaceCount;
+
+        FRIEND_TEST(LineBreakerTest, setLocales);
 };
 
 }  // namespace minikin
diff --git a/libs/minikin/Android.bp b/libs/minikin/Android.bp
index c0fbbea..df8ab9a 100644
--- a/libs/minikin/Android.bp
+++ b/libs/minikin/Android.bp
@@ -82,6 +82,7 @@
     header_libs: ["libminikin_headers"],
     export_header_lib_headers: ["libminikin_headers"],
     export_shared_lib_headers: ["libicuuc"],
+    whole_static_libs: ["libgtest_prod"],
 
     clang: true,
 }
diff --git a/libs/minikin/CmapCoverage.cpp b/libs/minikin/CmapCoverage.cpp
index c56d07c..c6318f5 100644
--- a/libs/minikin/CmapCoverage.cpp
+++ b/libs/minikin/CmapCoverage.cpp
@@ -254,6 +254,9 @@
         }
         if (end > MAX_UNICODE_CODE_POINT) {
             // file is inclusive, vector is exclusive
+            if (end == 0xFFFFFFFF) {
+                android_errorWriteLog(0x534e4554, "62134807");
+            }
             return addRange(coverage, start, MAX_UNICODE_CODE_POINT + 1);
         }
         if (!addRange(coverage, start, end + 1)) {  // file is inclusive, vector is exclusive
diff --git a/libs/minikin/FontCollection.cpp b/libs/minikin/FontCollection.cpp
index 871d974..9abe84d 100644
--- a/libs/minikin/FontCollection.cpp
+++ b/libs/minikin/FontCollection.cpp
@@ -296,22 +296,39 @@
     return vs == 0 ? mFamilies[mFamilyVec[bestFamilyIndex]] : mFamilies[bestFamilyIndex];
 }
 
-const uint32_t NBSP = 0x00A0;
-const uint32_t SOFT_HYPHEN = 0x00AD;
-const uint32_t ZWJ = 0x200C;
-const uint32_t ZWNJ = 0x200D;
-const uint32_t HYPHEN = 0x2010;
-const uint32_t NB_HYPHEN = 0x2011;
-const uint32_t NNBSP = 0x202F;
-const uint32_t FEMALE_SIGN = 0x2640;
-const uint32_t MALE_SIGN = 0x2642;
-const uint32_t STAFF_OF_AESCULAPIUS = 0x2695;
+// Characters where we want to continue using existing font run for (or stick to the next run if
+// they start a string), even if the font does not support them explicitly. These are handled
+// properly by Minikin or HarfBuzz even if the font does not explicitly support them and it's
+// usually meaningless to switch to a different font to display them.
+static bool doesNotNeedFontSupport(uint32_t c) {
+    return c == 0x00AD // SOFT HYPHEN
+            || c == 0x034F // COMBINING GRAPHEME JOINER
+            || c == 0x061C // ARABIC LETTER MARK
+            || (0x200C <= c && c <= 0x200F) // ZERO WIDTH NON-JOINER..RIGHT-TO-LEFT MARK
+            || (0x202A <= c && c <= 0x202E) // LEFT-TO-RIGHT EMBEDDING..RIGHT-TO-LEFT OVERRIDE
+            || (0x2066 <= c && c <= 0x2069) // LEFT-TO-RIGHT ISOLATE..POP DIRECTIONAL ISOLATE
+            || c == 0xFEFF // BYTE ORDER MARK
+            || isVariationSelector(c);
+}
 
 // Characters where we want to continue using existing font run instead of
 // recomputing the best match in the fallback list.
 static const uint32_t stickyWhitelist[] = {
-        '!', ',', '-', '.', ':', ';', '?', NBSP, ZWJ, ZWNJ,
-        HYPHEN, NB_HYPHEN, NNBSP, FEMALE_SIGN, MALE_SIGN, STAFF_OF_AESCULAPIUS };
+    '!',
+    ',',
+    '-',
+    '.',
+    ':',
+    ';',
+    '?',
+    0x00A0, // NBSP
+    0x2010, // HYPHEN
+    0x2011, // NB_HYPHEN
+    0x202F, // NNBSP
+    0x2640, // FEMALE_SIGN,
+    0x2642, // MALE_SIGN,
+    0x2695, // STAFF_OF_AESCULAPIUS
+};
 
 static bool isStickyWhitelisted(uint32_t c) {
     for (size_t i = 0; i < sizeof(stickyWhitelist) / sizeof(stickyWhitelist[0]); i++) {
@@ -320,6 +337,10 @@
     return false;
 }
 
+static inline bool isCombining(uint32_t c) {
+    return (U_GET_GC_MASK(c) & U_GC_M_MASK) != 0;
+}
+
 bool FontCollection::hasVariationSelector(uint32_t baseCodepoint,
         uint32_t variationSelector) const {
     if (!isVariationSelector(variationSelector)) {
@@ -355,12 +376,14 @@
     return false;
 }
 
+constexpr uint32_t REPLACEMENT_CHARACTER = 0xFFFD;
+
 void FontCollection::itemize(const uint16_t *string, size_t string_size, FontStyle style,
         vector<Run>* result) const {
     const uint32_t langListId = style.getLanguageListId();
     int variant = style.getVariant();
     const FontFamily* lastFamily = nullptr;
-    Run* run = NULL;
+    Run* run = nullptr;
 
     if (string_size == 0) {
         return;
@@ -373,6 +396,9 @@
     size_t nextUtf16Pos = 0;
     size_t readLength = 0;
     U16_NEXT(string, readLength, string_size, nextCh);
+    if (U_IS_SURROGATE(nextCh)) {
+        nextCh = REPLACEMENT_CHARACTER;
+    }
 
     do {
         const uint32_t ch = nextCh;
@@ -380,19 +406,20 @@
         nextUtf16Pos = readLength;
         if (readLength < string_size) {
             U16_NEXT(string, readLength, string_size, nextCh);
+            if (U_IS_SURROGATE(nextCh)) {
+                nextCh = REPLACEMENT_CHARACTER;
+            }
         } else {
             nextCh = kEndOfString;
         }
 
         bool shouldContinueRun = false;
-        if (lastFamily != nullptr) {
-            if (isStickyWhitelisted(ch)) {
-                // Continue using existing font as long as it has coverage and is whitelisted
-                shouldContinueRun = lastFamily->getCoverage().get(ch);
-            } else if (ch == SOFT_HYPHEN || isVariationSelector(ch)) {
-                // Always continue if the character is the soft hyphen or a variation selector.
-                shouldContinueRun = true;
-            }
+        if (doesNotNeedFontSupport(ch)) {
+            // Always continue if the character is a format character not needed to be in the font.
+            shouldContinueRun = true;
+        } else if (lastFamily != nullptr && (isStickyWhitelisted(ch) || isCombining(ch))) {
+            // Continue using existing font as long as it has coverage and is whitelisted.
+            shouldContinueRun = lastFamily->getCoverage().get(ch);
         }
 
         if (!shouldContinueRun) {
@@ -406,24 +433,40 @@
                 // character to the new run. U+20E3 COMBINING ENCLOSING KEYCAP, used in emoji, is
                 // handled properly by this since it's a combining mark too.
                 if (utf16Pos != 0 &&
-                        ((U_GET_GC_MASK(ch) & U_GC_M_MASK) != 0 ||
-                         (isEmojiModifier(ch) && isEmojiBase(prevCh))) &&
+                        (isCombining(ch) || (isEmojiModifier(ch) && isEmojiBase(prevCh))) &&
                         family != nullptr && family->getCoverage().get(prevCh)) {
                     const size_t prevChLength = U16_LENGTH(prevCh);
-                    run->end -= prevChLength;
-                    if (run->start == run->end) {
-                        result->pop_back();
+                    if (run != nullptr) {
+                        run->end -= prevChLength;
+                        if (run->start == run->end) {
+                            result->pop_back();
+                        }
                     }
                     start -= prevChLength;
                 }
+                if (lastFamily == nullptr) {
+                    // This is the first family ever assigned. We are either seeing the very first
+                    // character (which means start would already be zero), or we have only seen
+                    // characters that don't need any font support (which means we need to adjust
+                    // start to be 0 to include those characters).
+                    start = 0;
+                }
                 result->push_back({family->getClosestMatch(style), static_cast<int>(start), 0});
                 run = &result->back();
                 lastFamily = family.get();
             }
         }
         prevCh = ch;
-        run->end = nextUtf16Pos;  // exclusive
+        if (run != nullptr) {
+            run->end = nextUtf16Pos;  // exclusive
+        }
     } while (nextCh != kEndOfString);
+
+    if (lastFamily == nullptr) {
+        // No character needed any font support, so it doesn't really matter which font they end up
+        // getting displayed in. We put the whole string in one run, using the first font.
+        result->push_back({mFamilies[0]->getClosestMatch(style), 0, static_cast<int>(string_size)});
+    }
 }
 
 FakedFont FontCollection::baseFontFaked(FontStyle style) {
diff --git a/libs/minikin/Layout.cpp b/libs/minikin/Layout.cpp
index 4007007..cbfb430 100644
--- a/libs/minikin/Layout.cpp
+++ b/libs/minikin/Layout.cpp
@@ -326,26 +326,12 @@
 }
 
 static hb_codepoint_t decodeUtf16(const uint16_t* chars, size_t len, ssize_t* iter) {
-    const uint16_t v = chars[(*iter)++];
-    // test whether v in (0xd800..0xdfff), lead or trail surrogate
-    if ((v & 0xf800) == 0xd800) {
-        // test whether v in (0xd800..0xdbff), lead surrogate
-        if (size_t(*iter) < len && (v & 0xfc00) == 0xd800) {
-            const uint16_t v2 = chars[(*iter)++];
-            // test whether v2 in (0xdc00..0xdfff), trail surrogate
-            if ((v2 & 0xfc00) == 0xdc00) {
-                // (0xd800 0xdc00) in utf-16 maps to 0x10000 in ucs-32
-                const hb_codepoint_t delta = (0xd800 << 10) + 0xdc00 - 0x10000;
-                return (((hb_codepoint_t)v) << 10) + v2 - delta;
-            }
-            (*iter) -= 1;
-            return 0xFFFDu;
-        } else {
-            return 0xFFFDu;
-        }
-    } else {
-        return v;
+    UChar32 result;
+    U16_NEXT(chars, *iter, (ssize_t) len, result);
+    if (U_IS_SURROGATE(result)) { // isolated surrogate
+        result = 0xFFFDu; // U+FFFD REPLACEMENT CHARACTER
     }
+    return (hb_codepoint_t) result;
 }
 
 static hb_script_t getScriptRun(const uint16_t* chars, size_t len, ssize_t* iter) {
@@ -546,10 +532,15 @@
     if (!U_SUCCESS(status) || rc < 0) {
         ALOGW("error counting bidi runs, status = %d", status);
     }
-    if (!U_SUCCESS(status) || rc <= 1) {
+    if (!U_SUCCESS(status) || rc <= 0) {
         mIsRtl = (paraDir == kBidi_RTL);
         return;
     }
+    if (rc == 1) {
+        const UBiDiDirection runDir = ubidi_getVisualRun(mBidi, 0, nullptr, nullptr);
+        mIsRtl = (runDir == UBIDI_RTL);
+        return;
+    }
     mRunCount = rc;
 }
 
diff --git a/libs/minikin/LineBreaker.cpp b/libs/minikin/LineBreaker.cpp
index d247644..bccc299 100644
--- a/libs/minikin/LineBreaker.cpp
+++ b/libs/minikin/LineBreaker.cpp
@@ -61,10 +61,38 @@
 // Maximum amount that spaces can shrink, in justified text.
 const float SHRINKABILITY = 1.0 / 3.0;
 
-void LineBreaker::setLocale(const icu::Locale& locale, Hyphenator* hyphenator) {
-    mWordBreaker.setLocale(locale);
-    mLocale = locale;
-    mHyphenator = hyphenator;
+void LineBreaker::setLocales(const char* locales, const std::vector<Hyphenator*>& hyphenators) {
+    bool goodLocaleFound = false;
+    const ssize_t numLocales = hyphenators.size();
+    // For now, we ignore all locales except the first valid one.
+    // TODO: Support selecting the locale based on the script of the text.
+    const char* localeStart = locales;
+    for (ssize_t i = 0; i < numLocales - 1; i++) { // Loop over all locales, except the last one.
+        const char* localeEnd = strchr(localeStart, ',');
+        const size_t localeNameLength = localeEnd - localeStart;
+        char localeName[localeNameLength + 1];
+        strncpy(localeName, localeStart, localeNameLength);
+        localeName[localeNameLength] = '\0';
+        mLocale = icu::Locale::createFromName(localeName);
+        goodLocaleFound = !mLocale.isBogus();
+        if (goodLocaleFound) {
+            mHyphenator = hyphenators[i];
+            break;
+        } else {
+            localeStart = localeEnd + 1;
+        }
+    }
+    if (!goodLocaleFound) { // Try the last locale.
+        mLocale = icu::Locale::createFromName(localeStart);
+        if (mLocale.isBogus()) {
+            // No good locale.
+            mLocale = icu::Locale::getRoot();
+            mHyphenator = nullptr;
+        } else {
+            mHyphenator = numLocales == 0 ? nullptr : hyphenators[numLocales - 1];
+        }
+    }
+    mWordBreaker.setLocale(mLocale);
 }
 
 void LineBreaker::setText() {
diff --git a/tests/Android.bp b/tests/Android.bp
index ddc5012..887f057 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -1,8 +1,10 @@
 filegroup {
     name: "minikin-test-data",
     srcs: [
+        "data/Arabic.ttf",
         "data/Bold.ttf",
         "data/BoldItalic.ttf",
+        "data/Cherokee.ttf",
         "data/ColorEmojiFont.ttf",
         "data/ColorTextMixedEmojiFont.ttf",
         "data/Emoji.ttf",
diff --git a/tests/data/Arabic.ttf b/tests/data/Arabic.ttf
new file mode 100644
index 0000000..faa1f3d
--- /dev/null
+++ b/tests/data/Arabic.ttf
Binary files differ
diff --git a/tests/data/Arabic.ttx b/tests/data/Arabic.ttx
new file mode 100644
index 0000000..778af33
--- /dev/null
+++ b/tests/data/Arabic.ttx
@@ -0,0 +1,196 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.9">
+
+  <GlyphOrder>
+    <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="uni061C"/>
+    <GlyphID id="2" name="uni200D"/>
+  </GlyphOrder>
+
+  <head>
+    <!-- Most of this table will be recalculated by the compiler -->
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x27902029"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Wed Sep  9 08:01:17 2015"/>
+    <modified value="Mon Jun 12 22:51:58 2017"/>
+    <xMin value="0"/>
+    <yMin value="0"/>
+    <xMax value="0"/>
+    <yMax value="0"/>
+    <macStyle value="00000000 00000000"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <indexToLocFormat value="0"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="0x00010000"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <advanceWidthMax value="500"/>
+    <minLeftSideBearing value="0"/>
+    <minRightSideBearing value="0"/>
+    <xMaxExtent value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+    <numberOfHMetrics value="1"/>
+  </hhea>
+
+  <maxp>
+    <!-- Most of this table will be recalculated by the compiler -->
+    <tableVersion value="0x10000"/>
+    <numGlyphs value="3"/>
+    <maxPoints value="0"/>
+    <maxContours value="0"/>
+    <maxCompositePoints value="0"/>
+    <maxCompositeContours value="0"/>
+    <maxZones value="2"/>
+    <maxTwilightPoints value="12"/>
+    <maxStorage value="28"/>
+    <maxFunctionDefs value="119"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="61"/>
+    <maxSizeOfInstructions value="2967"/>
+    <maxComponentElements value="0"/>
+    <maxComponentDepth value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="400"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 01000000"/>
+    <usFirstCharIndex value="1564"/>
+    <usLastCharIndex value="8205"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="uni061C" width="0" lsb="0"/>
+    <mtx name="uni200D" width="0" lsb="0"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_4 platformID="3" platEncID="10" language="0">
+      <map code="0x61c" name="uni061C"/><!-- ARABIC LETTER MARK -->
+      <map code="0x200d" name="uni200D"/><!-- ZERO WIDTH JOINER -->
+    </cmap_format_4>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+
+    <!-- The xMin, yMin, xMax and yMax values
+         will be recalculated by the compiler. -->
+
+    <TTGlyph name=".notdef"/><!-- contains no outline data -->
+
+    <TTGlyph name="uni061C"/><!-- contains no outline data -->
+
+    <TTGlyph name="uni200D"/><!-- contains no outline data -->
+
+  </glyf>
+
+  <name>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      ArabicFont Test
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      ArabicFont Test
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      ArabicFontTest-Regular
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/data/Cherokee.ttf b/tests/data/Cherokee.ttf
new file mode 100644
index 0000000..f66252e
--- /dev/null
+++ b/tests/data/Cherokee.ttf
Binary files differ
diff --git a/tests/data/Cherokee.ttx b/tests/data/Cherokee.ttx
new file mode 100644
index 0000000..71d80f0
--- /dev/null
+++ b/tests/data/Cherokee.ttx
@@ -0,0 +1,196 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.9">
+
+  <GlyphOrder>
+    <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="uni0301"/>
+    <GlyphID id="2" name="uni13A0"/>
+  </GlyphOrder>
+
+  <head>
+    <!-- Most of this table will be recalculated by the compiler -->
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x27902029"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Wed Sep  9 08:01:17 2015"/>
+    <modified value="Mon Jun 12 22:51:58 2017"/>
+    <xMin value="0"/>
+    <yMin value="0"/>
+    <xMax value="0"/>
+    <yMax value="0"/>
+    <macStyle value="00000000 00000000"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <indexToLocFormat value="0"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="0x00010000"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <advanceWidthMax value="500"/>
+    <minLeftSideBearing value="0"/>
+    <minRightSideBearing value="0"/>
+    <xMaxExtent value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+    <numberOfHMetrics value="1"/>
+  </hhea>
+
+  <maxp>
+    <!-- Most of this table will be recalculated by the compiler -->
+    <tableVersion value="0x10000"/>
+    <numGlyphs value="3"/>
+    <maxPoints value="0"/>
+    <maxContours value="0"/>
+    <maxCompositePoints value="0"/>
+    <maxCompositeContours value="0"/>
+    <maxZones value="2"/>
+    <maxTwilightPoints value="12"/>
+    <maxStorage value="28"/>
+    <maxFunctionDefs value="119"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="61"/>
+    <maxSizeOfInstructions value="2967"/>
+    <maxComponentElements value="0"/>
+    <maxComponentDepth value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="400"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 01000000"/>
+    <usFirstCharIndex value="1564"/>
+    <usLastCharIndex value="8205"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="0"/>
+    <mtx name="uni0301" width="0" lsb="0"/>
+    <mtx name="uni13A0" width="0" lsb="0"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_4 platformID="3" platEncID="10" language="0">
+      <map code="0x0301" name="uni0301"/><!-- COMBINING ACUTE ACCENT -->
+      <map code="0x13A0" name="uni13A0"/><!-- CHEROKEE LETTER A -->
+    </cmap_format_4>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+
+    <!-- The xMin, yMin, xMax and yMax values
+         will be recalculated by the compiler. -->
+
+    <TTGlyph name=".notdef"/><!-- contains no outline data -->
+
+    <TTGlyph name="uni0301"/><!-- contains no outline data -->
+
+    <TTGlyph name="uni13A0"/><!-- contains no outline data -->
+
+  </glyf>
+
+  <name>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      CherokeeFont Test
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      CherokeeFont Test
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      CherokeeFontTest-Regular
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/data/Emoji.ttf b/tests/data/Emoji.ttf
index a3413b3..d0ae55d 100644
--- a/tests/data/Emoji.ttf
+++ b/tests/data/Emoji.ttf
Binary files differ
diff --git a/tests/data/Emoji.ttx b/tests/data/Emoji.ttx
index 3318c59..2e9b52d 100644
--- a/tests/data/Emoji.ttx
+++ b/tests/data/Emoji.ttx
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.9">
 
   <GlyphOrder>
     <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
@@ -24,6 +24,7 @@
     <GlyphID id="4" name="0"/>
     <GlyphID id="5" name="U+1F470"/>
     <GlyphID id="6" name="U+203C_VS16"/>
+    <GlyphID id="7" name="U+200D"/>
   </GlyphOrder>
 
   <head>
@@ -36,10 +37,10 @@
     <unitsPerEm value="1000"/>
     <created value="Wed Sep  9 08:01:17 2015"/>
     <modified value="Wed Sep  9 08:48:07 2015"/>
-    <xMin value="30"/>
-    <yMin value="-200"/>
-    <xMax value="629"/>
-    <yMax value="800"/>
+    <xMin value="0"/>
+    <yMin value="0"/>
+    <xMax value="0"/>
+    <yMax value="0"/>
     <macStyle value="00000000 00000000"/>
     <lowestRecPPEM value="7"/>
     <fontDirectionHint value="2"/>
@@ -48,14 +49,14 @@
   </head>
 
   <hhea>
-    <tableVersion value="1.0"/>
+    <tableVersion value="0x00010000"/>
     <ascent value="1000"/>
     <descent value="-200"/>
     <lineGap value="0"/>
-    <advanceWidthMax value="659"/>
+    <advanceWidthMax value="500"/>
     <minLeftSideBearing value="0"/>
-    <minRightSideBearing value="30"/>
-    <xMaxExtent value="629"/>
+    <minRightSideBearing value="0"/>
+    <xMaxExtent value="0"/>
     <caretSlopeRise value="1"/>
     <caretSlopeRun value="0"/>
     <caretOffset value="0"/>
@@ -64,15 +65,15 @@
     <reserved2 value="0"/>
     <reserved3 value="0"/>
     <metricDataFormat value="0"/>
-    <numberOfHMetrics value="18"/>
+    <numberOfHMetrics value="1"/>
   </hhea>
 
   <maxp>
     <!-- Most of this table will be recalculated by the compiler -->
     <tableVersion value="0x10000"/>
-    <numGlyphs value="54"/>
-    <maxPoints value="73"/>
-    <maxContours value="10"/>
+    <numGlyphs value="8"/>
+    <maxPoints value="0"/>
+    <maxContours value="0"/>
     <maxCompositePoints value="0"/>
     <maxCompositeContours value="0"/>
     <maxZones value="2"/>
@@ -123,8 +124,8 @@
     <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
     <achVendID value="UKWN"/>
     <fsSelection value="00000000 01000000"/>
-    <usFirstCharIndex value="32"/>
-    <usLastCharIndex value="122"/>
+    <usFirstCharIndex value="48"/>
+    <usLastCharIndex value="65535"/>
     <sTypoAscender value="800"/>
     <sTypoDescender value="-200"/>
     <sTypoLineGap value="200"/>
@@ -147,15 +148,17 @@
     <mtx name="0" width="500" lsb="93"/>
     <mtx name="U+1F470" width="500" lsb="93"/>
     <mtx name="U+203C_VS16" width="500" lsb="93"/>
+    <mtx name="U+200D" width="500" lsb="93"/>
   </hmtx>
 
   <cmap>
     <tableVersion version="0"/>
-    <cmap_format_12 format="12" reserved="0" length="3" nGroups="1" platformID="3" platEncID="1" language="0">
-      <map code="0x1F469" name="U+1F469" />
-      <map code="0x1F467" name="U+1F467" />
-      <map code="0x20E3" name="U+20E3" />
+    <cmap_format_12 format="12" reserved="0" length="3" nGroups="6" platformID="3" platEncID="1" language="0">
       <map code="0x0030" name="0" />
+      <map code="0x200D" name="U+200D" />
+      <map code="0x20E3" name="U+20E3" />
+      <map code="0x1F467" name="U+1F467" />
+      <map code="0x1F469" name="U+1F469" />
       <map code="0x1F470" name="U+1F470" />
     </cmap_format_12>
     <cmap_format_14 format="14" platformID="0" platEncID="5" length="30" numVarSelectorRecords="1">
@@ -172,43 +175,24 @@
     <!-- The xMin, yMin, xMax and yMax values
          will be recalculated by the compiler. -->
 
-    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0">
-      <contour></contour><instructions><assembly></assembly></instructions>
-    </TTGlyph>
+    <TTGlyph name=".notdef"/><!-- contains no outline data -->
 
-    <TTGlyph name="U+1F469" xMin="0" yMin="0" xMax="0" yMax="0">
-      <contour></contour><instructions><assembly></assembly></instructions>
-    </TTGlyph>
-    <TTGlyph name="U+1F467" xMin="0" yMin="0" xMax="0" yMax="0">
-      <contour></contour><instructions><assembly></assembly></instructions>
-    </TTGlyph>
-    <TTGlyph name="U+20E3" xMin="0" yMin="0" xMax="0" yMax="0">
-      <contour></contour><instructions><assembly></assembly></instructions>
-    </TTGlyph>
-    <TTGlyph name="0" xMin="0" yMin="0" xMax="0" yMax="0">
-      <contour></contour><instructions><assembly></assembly></instructions>
-    </TTGlyph>
-    <TTGlyph name="U+1F470" xMin="0" yMin="0" xMax="0" yMax="0">
-      <contour></contour><instructions><assembly></assembly></instructions>
-    </TTGlyph>
-    <TTGlyph name="U+203C_VS16" xMin="0" yMin="0" xMax="0" yMax="0">
-      <contour></contour><instructions><assembly></assembly></instructions>
-    </TTGlyph>
+    <TTGlyph name="U+1F469"/><!-- contains no outline data -->
+
+    <TTGlyph name="U+1F467"/><!-- contains no outline data -->
+
+    <TTGlyph name="U+20E3"/><!-- contains no outline data -->
+
+    <TTGlyph name="0"/><!-- contains no outline data -->
+
+    <TTGlyph name="U+1F470"/><!-- contains no outline data -->
+
+    <TTGlyph name="U+203C_VS16"/><!-- contains no outline data -->
+
+    <TTGlyph name="U+200D"/><!-- contains no outline data -->
   </glyf>
 
   <name>
-    <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
-      EmojiFont Test
-    </namerecord>
-    <namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
-      Regular
-    </namerecord>
-    <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
-      EmojiFont Test
-    </namerecord>
-    <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
-      EmojiFontTest-Regular
-    </namerecord>
     <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
       EmojiFont Test
     </namerecord>
diff --git a/tests/data/itemize.xml b/tests/data/itemize.xml
index 32a5ab0..722f923 100644
--- a/tests/data/itemize.xml
+++ b/tests/data/itemize.xml
@@ -21,6 +21,12 @@
         <font weight="700" style="italic">BoldItalic.ttf</font>
     </family>
 
+    <family lang="und-Arab">
+        <font weight="400" style="normal">Arabic.ttf</font>
+    </family>
+    <family lang="und-Cher">
+        <font weight="400" style="normal">Cherokee.ttf</font>
+    </family>
     <family lang="zh-Hans">
         <font weight="400" style="normal">ZhHans.ttf</font>
     </family>
diff --git a/tests/perftests/how_to_run.txt b/tests/perftests/how_to_run.txt
index f55a8ac..e7d268c 100644
--- a/tests/perftests/how_to_run.txt
+++ b/tests/perftests/how_to_run.txt
@@ -1,3 +1,3 @@
-mmm -j8 frameworks/minikin/tests/perftests &&
+mmm -j frameworks/minikin/tests/perftests &&
 adb sync data &&
 adb shell /data/benchmarktest/minikin_perftests/minikin_perftests
diff --git a/tests/stresstest/how_to_run.txt b/tests/stresstest/how_to_run.txt
index ba4dbdf..853d363 100644
--- a/tests/stresstest/how_to_run.txt
+++ b/tests/stresstest/how_to_run.txt
@@ -1,3 +1,3 @@
-mmm -j8 frameworks/minikin/tests/stresstest &&
+mmm -j frameworks/minikin/tests/stresstest &&
 adb sync data &&
 adb shell /data/nativetest/minikin_tests/minikin_stress_tests
diff --git a/tests/unittest/Android.bp b/tests/unittest/Android.bp
index 2353d4c..348d186 100644
--- a/tests/unittest/Android.bp
+++ b/tests/unittest/Android.bp
@@ -51,6 +51,7 @@
         "GraphemeBreakTests.cpp",
         "LayoutTest.cpp",
         "LayoutUtilsTest.cpp",
+        "LineBreakerTest.cpp",
         "MeasurementTests.cpp",
         "SparseBitSetTest.cpp",
         "UnicodeUtilsTest.cpp",
diff --git a/tests/unittest/FontCollectionItemizeTest.cpp b/tests/unittest/FontCollectionItemizeTest.cpp
index 78bfa3b..a031908 100644
--- a/tests/unittest/FontCollectionItemizeTest.cpp
+++ b/tests/unittest/FontCollectionItemizeTest.cpp
@@ -30,6 +30,7 @@
 namespace minikin {
 
 const char kItemizeFontXml[] = kTestFontDir "itemize.xml";
+const char kCherokeeFont[] = kTestFontDir "Cherokee.ttf";
 const char kEmojiFont[] = kTestFontDir "Emoji.ttf";
 const char kJAFont[] = kTestFontDir "Ja.ttf";
 const char kKOFont[] = kTestFontDir "Ko.ttf";
@@ -135,7 +136,7 @@
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
 
-    // U+0301(COMBINING ACUTE ACCENT) must be in the same run with preceding
+    // U+0301 (COMBINING ACUTE ACCENT) must be in the same run with preceding
     // chars if the font supports it.
     itemize(collection, "'a' U+0301", kRegularStyle, &runs);
     ASSERT_EQ(1U, runs.size());
@@ -146,6 +147,51 @@
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
 }
 
+TEST_F(FontCollectionItemizeTest, itemize_combining) {
+    // The regular font and the Cherokee font both support U+0301 (COMBINING ACUTE ACCENT). Since
+    // it's a combining mark, it should come from whatever font the base character comes from.
+    std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kItemizeFontXml));
+    std::vector<FontCollection::Run> runs;
+
+    const FontStyle kRegularStyle = FontStyle();
+
+    itemize(collection, "'a' U+0301", kRegularStyle, &runs);
+    ASSERT_EQ(1U, runs.size());
+    EXPECT_EQ(0, runs[0].start);
+    EXPECT_EQ(2, runs[0].end);
+    EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+
+    // CHEROKEE LETTER A, COMBINING ACUTE ACCENT
+    itemize(collection, "U+13A0 U+0301", kRegularStyle, &runs);
+    ASSERT_EQ(1U, runs.size());
+    EXPECT_EQ(0, runs[0].start);
+    EXPECT_EQ(2, runs[0].end);
+    EXPECT_EQ(kCherokeeFont, getFontPath(runs[0]));
+
+    // CHEROKEE LETTER A, COMBINING ACUTE ACCENT, COMBINING ACUTE ACCENT
+    itemize(collection, "U+13A0 U+0301 U+0301", kRegularStyle, &runs);
+    ASSERT_EQ(1U, runs.size());
+    EXPECT_EQ(0, runs[0].start);
+    EXPECT_EQ(3, runs[0].end);
+    EXPECT_EQ(kCherokeeFont, getFontPath(runs[0]));
+
+    itemize(collection, "U+0301", kRegularStyle, &runs);
+    ASSERT_EQ(1U, runs.size());
+    EXPECT_EQ(0, runs[0].start);
+    EXPECT_EQ(1, runs[0].end);
+    EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+
+    // COMBINING ACUTE ACCENT, CHEROKEE LETTER A, COMBINING ACUTE ACCENT
+    itemize(collection, "U+0301 U+13A0 U+0301", kRegularStyle, &runs);
+    ASSERT_EQ(2U, runs.size());
+    EXPECT_EQ(0, runs[0].start);
+    EXPECT_EQ(1, runs[0].end);
+    EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+    EXPECT_EQ(1, runs[1].start);
+    EXPECT_EQ(3, runs[1].end);
+    EXPECT_EQ(kCherokeeFont, getFontPath(runs[1]));
+}
+
 TEST_F(FontCollectionItemizeTest, itemize_emoji) {
     std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kItemizeFontXml));
     std::vector<FontCollection::Run> runs;
@@ -691,6 +737,73 @@
     EXPECT_EQ(kVSTestFont, getFontPath(runs[0]));
 }
 
+TEST_F(FontCollectionItemizeTest, itemize_format_chars) {
+    std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kItemizeFontXml));
+    std::vector<FontCollection::Run> runs;
+
+    const FontStyle kDefaultFontStyle;
+
+    itemize(collection, "'a' U+061C 'b'", kDefaultFontStyle, &runs);
+    ASSERT_EQ(1U, runs.size());
+    EXPECT_EQ(0, runs[0].start);
+    EXPECT_EQ(3, runs[0].end);
+    EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+
+    itemize(collection, "'a' U+200D 'b'", kDefaultFontStyle, &runs);
+    ASSERT_EQ(1U, runs.size());
+    EXPECT_EQ(0, runs[0].start);
+    EXPECT_EQ(3, runs[0].end);
+    EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+
+    itemize(collection, "U+3042 U+061C U+3042", kDefaultFontStyle, &runs);
+    ASSERT_EQ(1U, runs.size());
+    EXPECT_EQ(0, runs[0].start);
+    EXPECT_EQ(3, runs[0].end);
+    EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+
+    itemize(collection, "U+061C 'b'", kDefaultFontStyle, &runs);
+    ASSERT_EQ(1U, runs.size());
+    EXPECT_EQ(0, runs[0].start);
+    EXPECT_EQ(2, runs[0].end);
+    EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+
+    itemize(collection, "U+061C U+3042", kDefaultFontStyle, &runs);
+    ASSERT_EQ(1U, runs.size());
+    EXPECT_EQ(0, runs[0].start);
+    EXPECT_EQ(2, runs[0].end);
+    EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+
+    itemize(collection, "U+061C", kDefaultFontStyle, &runs);
+    ASSERT_EQ(1U, runs.size());
+    EXPECT_EQ(0, runs[0].start);
+    EXPECT_EQ(1, runs[0].end);
+    EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+
+    itemize(collection, "U+061C U+061C U+061C", kDefaultFontStyle, &runs);
+    ASSERT_EQ(1U, runs.size());
+    EXPECT_EQ(0, runs[0].start);
+    EXPECT_EQ(3, runs[0].end);
+    EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+
+    itemize(collection, "U+200D U+20E3", kDefaultFontStyle, &runs);
+    ASSERT_EQ(1U, runs.size());
+    EXPECT_EQ(0, runs[0].start);
+    EXPECT_EQ(2, runs[0].end);
+    EXPECT_EQ(kEmojiFont, getFontPath(runs[0]));
+
+    itemize(collection, "U+200D", kDefaultFontStyle, &runs);
+    ASSERT_EQ(1U, runs.size());
+    EXPECT_EQ(0, runs[0].start);
+    EXPECT_EQ(1, runs[0].end);
+    EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+
+    itemize(collection, "U+20E3", kDefaultFontStyle, &runs);
+    ASSERT_EQ(1U, runs.size());
+    EXPECT_EQ(0, runs[0].start);
+    EXPECT_EQ(1, runs[0].end);
+    EXPECT_EQ(kEmojiFont, getFontPath(runs[0]));
+}
+
 TEST_F(FontCollectionItemizeTest, itemize_LanguageScore) {
     struct TestCase {
         std::string userPreferredLanguages;
diff --git a/tests/unittest/LayoutTest.cpp b/tests/unittest/LayoutTest.cpp
index 1770d3a..f89ac9e 100644
--- a/tests/unittest/LayoutTest.cpp
+++ b/tests/unittest/LayoutTest.cpp
@@ -347,18 +347,19 @@
     }
 }
 
+// Test that a forced-RTL layout correctly mirros a forced-LTR layout.
 TEST_F(LayoutTest, doLayoutTest_rtlTest) {
     MinikinPaint paint;
 
     std::vector<uint16_t> text = parseUnicodeString("'a' 'b' U+3042 U+3043 'c' 'd'");
 
     Layout ltrLayout;
-    ltrLayout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
-            mCollection);
+    ltrLayout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_Force_LTR, FontStyle(),
+            paint, mCollection);
 
     Layout rtlLayout;
-    rtlLayout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_RTL, FontStyle(), paint,
-            mCollection);
+    rtlLayout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_Force_RTL, FontStyle(),
+            paint, mCollection);
 
     ASSERT_EQ(ltrLayout.nGlyphs(), rtlLayout.nGlyphs());
     ASSERT_EQ(6u, ltrLayout.nGlyphs());
@@ -370,6 +371,38 @@
     }
 }
 
+// Test that single-run RTL layouts of LTR-only text is laid out identical to an LTR layout.
+TEST_F(LayoutTest, singleRunBidiTest) {
+    MinikinPaint paint;
+
+    std::vector<uint16_t> text = parseUnicodeString("'1' '2' '3'");
+
+    Layout ltrLayout;
+    ltrLayout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(),
+            paint, mCollection);
+
+    Layout rtlLayout;
+    rtlLayout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_RTL, FontStyle(),
+            paint, mCollection);
+
+    Layout defaultRtlLayout;
+    defaultRtlLayout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_Default_RTL,
+            FontStyle(), paint, mCollection);
+
+    const size_t nGlyphs = ltrLayout.nGlyphs();
+    ASSERT_EQ(3u, nGlyphs);
+
+    ASSERT_EQ(nGlyphs, rtlLayout.nGlyphs());
+    ASSERT_EQ(nGlyphs, defaultRtlLayout.nGlyphs());
+
+    for (size_t i = 0; i < nGlyphs; ++i) {
+        EXPECT_EQ(ltrLayout.getFont(i), rtlLayout.getFont(i));
+        EXPECT_EQ(ltrLayout.getGlyphId(i), rtlLayout.getGlyphId(i));
+        EXPECT_EQ(ltrLayout.getFont(i), defaultRtlLayout.getFont(i));
+        EXPECT_EQ(ltrLayout.getGlyphId(i), defaultRtlLayout.getGlyphId(i));
+    }
+}
+
 TEST_F(LayoutTest, hyphenationTest) {
     Layout layout;
     std::vector<uint16_t> text;
diff --git a/tests/unittest/LineBreakerTest.cpp b/tests/unittest/LineBreakerTest.cpp
new file mode 100644
index 0000000..4b18c8c
--- /dev/null
+++ b/tests/unittest/LineBreakerTest.cpp
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include "ICUTestBase.h"
+#include <minikin/LineBreaker.h>
+#include <unicode/locid.h>
+
+namespace minikin {
+
+typedef ICUTestBase LineBreakerTest;
+
+TEST_F(LineBreakerTest, setLocales) {
+    {
+        LineBreaker lineBreaker;
+        Hyphenator hyphenator;
+        std::vector<Hyphenator*> hyphenators;
+        hyphenators.push_back(&hyphenator);
+        lineBreaker.setLocales("en-US", hyphenators);
+        EXPECT_EQ(icu::Locale::getUS(), lineBreaker.mLocale);
+        EXPECT_EQ(&hyphenator, lineBreaker.mHyphenator);
+    }
+    {
+        LineBreaker lineBreaker;
+        Hyphenator hyphenator1, hyphenator2;
+        std::vector<Hyphenator*> hyphenators;
+        hyphenators.push_back(&hyphenator1);
+        hyphenators.push_back(&hyphenator2);
+        lineBreaker.setLocales("fr-FR,en-US", hyphenators);
+        EXPECT_EQ(icu::Locale::getFrance(), lineBreaker.mLocale);
+        EXPECT_EQ(&hyphenator1, lineBreaker.mHyphenator);
+    }
+    {
+        LineBreaker lineBreaker;
+        std::vector<Hyphenator*> hyphenators;
+        lineBreaker.setLocales("", hyphenators);
+        EXPECT_EQ(icu::Locale::getRoot(), lineBreaker.mLocale);
+        EXPECT_EQ(nullptr, lineBreaker.mHyphenator);
+    }
+    {
+        LineBreaker lineBreaker;
+        std::vector<Hyphenator*> hyphenators;
+        Hyphenator hyphenator;
+        hyphenators.push_back(&hyphenator);
+        lineBreaker.setLocales("THISISABOGUSLANGUAGE", hyphenators);
+        EXPECT_EQ(icu::Locale::getRoot(), lineBreaker.mLocale);
+        EXPECT_EQ(nullptr, lineBreaker.mHyphenator);
+    }
+    {
+        LineBreaker lineBreaker;
+        Hyphenator hyphenator1, hyphenator2;
+        std::vector<Hyphenator*> hyphenators;
+        hyphenators.push_back(&hyphenator1);
+        hyphenators.push_back(&hyphenator2);
+        lineBreaker.setLocales("THISISABOGUSLANGUAGE,en-US", hyphenators);
+        EXPECT_EQ(icu::Locale::getUS(), lineBreaker.mLocale);
+        EXPECT_EQ(&hyphenator2, lineBreaker.mHyphenator);
+    }
+    {
+        LineBreaker lineBreaker;
+        Hyphenator hyphenator1, hyphenator2;
+        std::vector<Hyphenator*> hyphenators;
+        hyphenators.push_back(&hyphenator1);
+        hyphenators.push_back(&hyphenator2);
+        lineBreaker.setLocales("THISISABOGUSLANGUAGE,ANOTHERBOGUSLANGUAGE", hyphenators);
+        EXPECT_EQ(icu::Locale::getRoot(), lineBreaker.mLocale);
+        EXPECT_EQ(nullptr, lineBreaker.mHyphenator);
+    }
+}
+
+}  // namespace minikin
diff --git a/tests/unittest/how_to_run.txt b/tests/unittest/how_to_run.txt
index 20aa5ab..dd40670 100644
--- a/tests/unittest/how_to_run.txt
+++ b/tests/unittest/how_to_run.txt
@@ -1,3 +1,3 @@
-mmm -j8 frameworks/minikin/tests/unittest &&
+mmm -j frameworks/minikin/tests/unittest &&
 adb sync data &&
 adb shell /data/nativetest/minikin_tests/minikin_tests