Merge "Use the non-LTO/PGO hwbinder in recovery_component_test"
diff --git a/tools/image_generator/ImageGenerator.java b/tools/image_generator/ImageGenerator.java
index 8cdd494..a011f10 100644
--- a/tools/image_generator/ImageGenerator.java
+++ b/tools/image_generator/ImageGenerator.java
@@ -50,504 +50,517 @@
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
 
-/**
- * Command line tool to generate the localized image for recovery mode.
- */
+/** Command line tool to generate the localized image for recovery mode. */
 public class ImageGenerator {
-  // Initial height of the image to draw.
-  private static final int INITIAL_HEIGHT = 20000;
+    // Initial height of the image to draw.
+    private static final int INITIAL_HEIGHT = 20000;
 
-  private static final float DEFAULT_FONT_SIZE = 40;
+    private static final float DEFAULT_FONT_SIZE = 40;
 
-  // This is the canvas we used to draw texts.
-  private BufferedImage mBufferedImage;
+    // This is the canvas we used to draw texts.
+    private BufferedImage mBufferedImage;
 
-  // The width in pixels of our image. Once set, its value won't change.
-  private final int mImageWidth;
+    // The width in pixels of our image. Once set, its value won't change.
+    private final int mImageWidth;
 
-  // The current height in pixels of our image. We will adjust the value when drawing more texts.
-  private int mImageHeight;
+    // The current height in pixels of our image. We will adjust the value when drawing more texts.
+    private int mImageHeight;
 
-  // The current vertical offset in pixels to draw the top edge of new text strings.
-  private int mVerticalOffset;
+    // The current vertical offset in pixels to draw the top edge of new text strings.
+    private int mVerticalOffset;
 
-  // The font size to draw the texts.
-  private final float mFontSize;
+    // The font size to draw the texts.
+    private final float mFontSize;
 
-  // The name description of the text to localize. It's used to find the translated strings in the
-  // resource file.
-  private final String mTextName;
+    // The name description of the text to localize. It's used to find the translated strings in the
+    // resource file.
+    private final String mTextName;
 
-  // The directory that contains all the needed font files (e.g. ttf, otf, ttc files).
-  private final String mFontDirPath;
+    // The directory that contains all the needed font files (e.g. ttf, otf, ttc files).
+    private final String mFontDirPath;
 
-  // An explicit map from language to the font name to use.
-  // The map is extracted from frameworks/base/data/fonts/fonts.xml.
-  // And the language-subtag-registry is found in:
-  // https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry
-  private static final String DEFAULT_FONT_NAME = "Roboto-Regular";
-  private static final Map<String, String> LANGUAGE_TO_FONT_MAP = new TreeMap<String, String>() {{
-    put("am", "NotoSansEthiopic-Regular");
-    put("ar", "NotoNaskhArabicUI-Regular");
-    put("as", "NotoSansBengaliUI-Regular");
-    put("bn", "NotoSansBengaliUI-Regular");
-    put("fa", "NotoNaskhArabicUI-Regular");
-    put("gu", "NotoSansGujaratiUI-Regular");
-    put("hi", "NotoSansDevanagariUI-Regular");
-    put("hy", "NotoSansArmenian-Regular");
-    put("iw", "NotoSansHebrew-Regular");
-    put("ja", "NotoSansCJK-Regular");
-    put("ka", "NotoSansGeorgian-Regular");
-    put("ko", "NotoSansCJK-Regular");
-    put("km", "NotoSansKhmerUI-Regular");
-    put("kn", "NotoSansKannadaUI-Regular");
-    put("lo", "NotoSansLaoUI-Regular");
-    put("ml", "NotoSansMalayalamUI-Regular");
-    put("mr", "NotoSansDevanagariUI-Regular");
-    put("my", "NotoSansMyanmarUI-Regular");
-    put("ne", "NotoSansDevanagariUI-Regular");
-    put("or", "NotoSansOriya-Regular");
-    put("pa", "NotoSansGurmukhiUI-Regular");
-    put("si", "NotoSansSinhala-Regular");
-    put("ta", "NotoSansTamilUI-Regular");
-    put("te", "NotoSansTeluguUI-Regular");
-    put("th", "NotoSansThaiUI-Regular");
-    put("ur", "NotoNaskhArabicUI-Regular");
-    put("zh", "NotoSansCJK-Regular");
-  }};
+    // An explicit map from language to the font name to use.
+    // The map is extracted from frameworks/base/data/fonts/fonts.xml.
+    // And the language-subtag-registry is found in:
+    // https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry
+    private static final String DEFAULT_FONT_NAME = "Roboto-Regular";
+    private static final Map<String, String> LANGUAGE_TO_FONT_MAP =
+            new TreeMap<String, String>() {
+                {
+                    put("am", "NotoSansEthiopic-Regular");
+                    put("ar", "NotoNaskhArabicUI-Regular");
+                    put("as", "NotoSansBengaliUI-Regular");
+                    put("bn", "NotoSansBengaliUI-Regular");
+                    put("fa", "NotoNaskhArabicUI-Regular");
+                    put("gu", "NotoSansGujaratiUI-Regular");
+                    put("hi", "NotoSansDevanagariUI-Regular");
+                    put("hy", "NotoSansArmenian-Regular");
+                    put("iw", "NotoSansHebrew-Regular");
+                    put("ja", "NotoSansCJK-Regular");
+                    put("ka", "NotoSansGeorgian-Regular");
+                    put("ko", "NotoSansCJK-Regular");
+                    put("km", "NotoSansKhmerUI-Regular");
+                    put("kn", "NotoSansKannadaUI-Regular");
+                    put("lo", "NotoSansLaoUI-Regular");
+                    put("ml", "NotoSansMalayalamUI-Regular");
+                    put("mr", "NotoSansDevanagariUI-Regular");
+                    put("my", "NotoSansMyanmarUI-Regular");
+                    put("ne", "NotoSansDevanagariUI-Regular");
+                    put("or", "NotoSansOriya-Regular");
+                    put("pa", "NotoSansGurmukhiUI-Regular");
+                    put("si", "NotoSansSinhala-Regular");
+                    put("ta", "NotoSansTamilUI-Regular");
+                    put("te", "NotoSansTeluguUI-Regular");
+                    put("th", "NotoSansThaiUI-Regular");
+                    put("ur", "NotoNaskhArabicUI-Regular");
+                    put("zh", "NotoSansCJK-Regular");
+                }
+            };
 
-  // Languages that write from right to left.
-  private static final Set<String> RTL_LANGUAGE = new HashSet<String>() {{
-    add("ar"); // Arabic
-    add("fa"); // Persian
-    add("he"); // Hebrew
-    add("iw"); // Hebrew
-    add("ur"); // Urdu
-  }};
+    // Languages that write from right to left.
+    private static final Set<String> RTL_LANGUAGE =
+            new HashSet<String>() {
+                {
+                    add("ar"); // Arabic
+                    add("fa"); // Persian
+                    add("he"); // Hebrew
+                    add("iw"); // Hebrew
+                    add("ur"); // Urdu
+                }
+            };
 
-  // Languages that breaks on arbitrary characters.
-  // TODO(xunchang) switch to icu library if possible.
-  private static final Set<String> LOGOGRAM_LANGUAGE = new HashSet<String>() {{
-    add("ja"); // Japanese
-    add("km"); // Khmer
-    add("ko"); // Korean
-    add("lo"); // Lao
-    add("zh"); // Chinese
-  }};
+    // Languages that breaks on arbitrary characters.
+    // TODO(xunchang) switch to icu library if possible.
+    private static final Set<String> LOGOGRAM_LANGUAGE =
+            new HashSet<String>() {
+                {
+                    add("ja"); // Japanese
+                    add("km"); // Khmer
+                    add("ko"); // Korean
+                    add("lo"); // Lao
+                    add("zh"); // Chinese
+                }
+            };
 
-  /**
-   * Exception to indicate the failure to find the translated text strings.
-   */
-  public static class LocalizedStringNotFoundException extends Exception {
-    public LocalizedStringNotFoundException(String message) {
-      super(message);
+    /** Exception to indicate the failure to find the translated text strings. */
+    public static class LocalizedStringNotFoundException extends Exception {
+        public LocalizedStringNotFoundException(String message) {
+            super(message);
+        }
+
+        public LocalizedStringNotFoundException(String message, Throwable cause) {
+            super(message, cause);
+        }
     }
 
-    public LocalizedStringNotFoundException(String message, Throwable cause) {
-      super(message, cause);
-    }
-  }
+    /** Initailizes the fields of the image image. */
+    public ImageGenerator(int imageWidth, String textName, float fontSize, String fontDirPath) {
+        mImageWidth = imageWidth;
+        mImageHeight = INITIAL_HEIGHT;
+        mVerticalOffset = 0;
 
-  /**
-   * Initailizes the fields of the image image.
-   */
-  public ImageGenerator(int imageWidth, String textName, float fontSize, String fontDirPath) {
-    mImageWidth = imageWidth;
-    mImageHeight = INITIAL_HEIGHT;
-    mVerticalOffset = 0;
+        // Initialize the canvas with the default height.
+        mBufferedImage = new BufferedImage(mImageWidth, mImageHeight, BufferedImage.TYPE_BYTE_GRAY);
 
-    // Initialize the canvas with the default height.
-    mBufferedImage = new BufferedImage(mImageWidth, mImageHeight, BufferedImage.TYPE_BYTE_GRAY);
-
-    mTextName = textName;
-    mFontSize = fontSize;
-    mFontDirPath = fontDirPath;
-  }
-
-  /**
-   * Finds the translated text string for the given textName by parsing the resourceFile.
-   * Example of the xml fields:
-   * <resources xmlns:android="http://schemas.android.com/apk/res/android">
-   *   <string name="recovery_installing_security" msgid="9184031299717114342">
-   * "Sicherheitsupdate wird installiert"</string>
-   * </resources>
-   *
-   * @param resourceFile the input resource file in xml format.
-   * @param textName the name description of the text.
-   *
-   * @return the string representation of the translated text.
-   */
-  private String getTextString(File resourceFile, String textName) throws IOException,
-      ParserConfigurationException, org.xml.sax.SAXException, LocalizedStringNotFoundException {
-    DocumentBuilderFactory builder = DocumentBuilderFactory.newInstance();
-    DocumentBuilder db = builder.newDocumentBuilder();
-
-    Document doc = db.parse(resourceFile);
-    doc.getDocumentElement().normalize();
-
-    NodeList nodeList = doc.getElementsByTagName("string");
-    for (int i = 0; i < nodeList.getLength(); i++) {
-      Node node = nodeList.item(i);
-      String name = node.getAttributes().getNamedItem("name").getNodeValue();
-      if (name.equals(textName)) {
-        return node.getTextContent();
-      }
+        mTextName = textName;
+        mFontSize = fontSize;
+        mFontDirPath = fontDirPath;
     }
 
-    throw new LocalizedStringNotFoundException(textName + " not found in "
-        + resourceFile.getName());
-  }
+    /**
+     * Finds the translated text string for the given textName by parsing the resourceFile. Example
+     * of the xml fields: <resources xmlns:android="http://schemas.android.com/apk/res/android">
+     * <string name="recovery_installing_security" msgid="9184031299717114342"> "Sicherheitsupdate
+     * wird installiert"</string> </resources>
+     *
+     * @param resourceFile the input resource file in xml format.
+     * @param textName the name description of the text.
+     * @return the string representation of the translated text.
+     */
+    private String getTextString(File resourceFile, String textName)
+            throws IOException, ParserConfigurationException, org.xml.sax.SAXException,
+                    LocalizedStringNotFoundException {
+        DocumentBuilderFactory builder = DocumentBuilderFactory.newInstance();
+        DocumentBuilder db = builder.newDocumentBuilder();
 
-  /**
-   * Constructs the locale from the name of the resource file.
-   */
-  private Locale getLocaleFromFilename(String filename) throws IOException {
-    // Gets the locale string by trimming the top "values-".
-    String localeString = filename.substring(7);
-    if (localeString.matches("[A-Za-z]+")) {
-      return Locale.forLanguageTag(localeString);
-    }
-    if (localeString.matches("[A-Za-z]+-r[A-Za-z]+")) {
-      // "${Language}-r${Region}". e.g. en-rGB
-      String[] tokens = localeString.split("-r");
-      return Locale.forLanguageTag(String.join("-", tokens));
-    }
-    if (localeString.startsWith("b+")) {
-      // The special case of b+sr+Latn, which has the form "b+${Language}+${ScriptName}"
-      String[] tokens = localeString.substring(2).split("\\+");
-      return Locale.forLanguageTag(String.join("-", tokens));
-    }
+        Document doc = db.parse(resourceFile);
+        doc.getDocumentElement().normalize();
 
-    throw new IOException("Unrecognized locale string " + localeString);
-  }
+        NodeList nodeList = doc.getElementsByTagName("string");
+        for (int i = 0; i < nodeList.getLength(); i++) {
+            Node node = nodeList.item(i);
+            String name = node.getAttributes().getNamedItem("name").getNodeValue();
+            if (name.equals(textName)) {
+                return node.getTextContent();
+            }
+        }
 
-  /**
-   * Iterates over the xml files in the format of values-$LOCALE/strings.xml under the resource
-   * directory and collect the translated text.
-   *
-   * @param resourcePath the path to the resource directory
-   *
-   * @return a map with the locale as key, and translated text as value
-   *
-   * @throws LocalizedStringNotFoundException if we cannot find the translated text for the given
-   *    locale
-   **/
-  public Map<Locale, String> readLocalizedStringFromXmls(String resourcePath) throws
-      IOException, LocalizedStringNotFoundException {
-    File resourceDir = new File(resourcePath);
-    if (!resourceDir.isDirectory()) {
-      throw new LocalizedStringNotFoundException(resourcePath + " is not a directory.");
-    }
-
-    Map<Locale, String> result =
-        // Overrides the string comparator so that sr is sorted behind sr-Latn. And thus recovery
-        // can find the most relevant locale when going down the list.
-        new TreeMap<>((Locale l1, Locale l2) -> {
-          if (l1.toLanguageTag().equals(l2.toLanguageTag())) {
-            return 0;
-          }
-          if (l1.getLanguage().equals(l2.toLanguageTag())) {
-            return -1;
-          }
-          if (l2.getLanguage().equals(l1.toLanguageTag())) {
-            return 1;
-          }
-          return l1.toLanguageTag().compareTo(l2.toLanguageTag());
-        });
-
-    // Find all the localized resource subdirectories in the format of values-$LOCALE
-    String[] nameList = resourceDir.list(
-        (File file, String name) -> name.startsWith("values-"));
-    for (String name : nameList) {
-      File textFile = new File(resourcePath, name + "/strings.xml");
-      String localizedText;
-      try {
-        localizedText = getTextString(textFile, mTextName);
-      } catch (IOException | ParserConfigurationException | org.xml.sax.SAXException e) {
         throw new LocalizedStringNotFoundException(
-            "Failed to read the translated text for locale " + name, e);
-      }
-
-      Locale locale = getLocaleFromFilename(name);
-      // Removes the double quotation mark from the text.
-      result.put(locale, localizedText.substring(1, localizedText.length() - 1));
+                textName + " not found in " + resourceFile.getName());
     }
 
-    return result;
-  }
+    /** Constructs the locale from the name of the resource file. */
+    private Locale getLocaleFromFilename(String filename) throws IOException {
+        // Gets the locale string by trimming the top "values-".
+        String localeString = filename.substring(7);
+        if (localeString.matches("[A-Za-z]+")) {
+            return Locale.forLanguageTag(localeString);
+        }
+        if (localeString.matches("[A-Za-z]+-r[A-Za-z]+")) {
+            // "${Language}-r${Region}". e.g. en-rGB
+            String[] tokens = localeString.split("-r");
+            return Locale.forLanguageTag(String.join("-", tokens));
+        }
+        if (localeString.startsWith("b+")) {
+            // The special case of b+sr+Latn, which has the form "b+${Language}+${ScriptName}"
+            String[] tokens = localeString.substring(2).split("\\+");
+            return Locale.forLanguageTag(String.join("-", tokens));
+        }
 
-  /**
-   * Returns a font object associated given the given locale
-   *
-   * @throws IOException if the font file fails to open
-   * @throws FontFormatException if the font file doesn't have the expected format
-   */
-  private Font loadFontsByLocale(String language) throws IOException, FontFormatException {
-    String fontName = LANGUAGE_TO_FONT_MAP.getOrDefault(language, DEFAULT_FONT_NAME);
-    String[] suffixes = {".otf", ".ttf", ".ttc"};
-    for (String suffix : suffixes ) {
-      File fontFile = new File(mFontDirPath, fontName + suffix);
-      if (fontFile.isFile()) {
-        return Font.createFont(Font.TRUETYPE_FONT, fontFile).deriveFont(mFontSize);
-      }
+        throw new IOException("Unrecognized locale string " + localeString);
     }
 
-    throw new IOException("Can not find the font file " + fontName + " for language " + language);
-  }
+    /**
+     * Iterates over the xml files in the format of values-$LOCALE/strings.xml under the resource
+     * directory and collect the translated text.
+     *
+     * @param resourcePath the path to the resource directory
+     * @return a map with the locale as key, and translated text as value
+     * @throws LocalizedStringNotFoundException if we cannot find the translated text for the given
+     *     locale
+     */
+    public Map<Locale, String> readLocalizedStringFromXmls(String resourcePath)
+            throws IOException, LocalizedStringNotFoundException {
+        File resourceDir = new File(resourcePath);
+        if (!resourceDir.isDirectory()) {
+            throw new LocalizedStringNotFoundException(resourcePath + " is not a directory.");
+        }
 
-  /**
-   * Separates the text string by spaces and wraps it by words.
-  **/
-  private List<String> wrapTextByWords(String text, FontMetrics metrics) {
-    List<String> wrappedText = new ArrayList<>();
-    StringTokenizer st = new StringTokenizer(text, " \n");
+        Map<Locale, String> result =
+                // Overrides the string comparator so that sr is sorted behind sr-Latn. And thus
+                // recovery can find the most relevant locale when going down the list.
+                new TreeMap<>(
+                        (Locale l1, Locale l2) -> {
+                            if (l1.toLanguageTag().equals(l2.toLanguageTag())) {
+                                return 0;
+                            }
+                            if (l1.getLanguage().equals(l2.toLanguageTag())) {
+                                return -1;
+                            }
+                            if (l2.getLanguage().equals(l1.toLanguageTag())) {
+                                return 1;
+                            }
+                            return l1.toLanguageTag().compareTo(l2.toLanguageTag());
+                        });
 
-    // TODO(xunchang). We assume that all words can fit on the screen. Raise an
-    // IllegalStateException if the word is wider than the image width.
-    StringBuilder line = new StringBuilder();
-    while (st.hasMoreTokens()) {
-      String token = st.nextToken();
-      if (metrics.stringWidth(line + token + " ") > mImageWidth) {
+        // Find all the localized resource subdirectories in the format of values-$LOCALE
+        String[] nameList =
+                resourceDir.list((File file, String name) -> name.startsWith("values-"));
+        for (String name : nameList) {
+            File textFile = new File(resourcePath, name + "/strings.xml");
+            String localizedText;
+            try {
+                localizedText = getTextString(textFile, mTextName);
+            } catch (IOException | ParserConfigurationException | org.xml.sax.SAXException e) {
+                throw new LocalizedStringNotFoundException(
+                        "Failed to read the translated text for locale " + name, e);
+            }
+
+            Locale locale = getLocaleFromFilename(name);
+            // Removes the double quotation mark from the text.
+            result.put(locale, localizedText.substring(1, localizedText.length() - 1));
+        }
+
+        return result;
+    }
+
+    /**
+     * Returns a font object associated given the given locale
+     *
+     * @throws IOException if the font file fails to open
+     * @throws FontFormatException if the font file doesn't have the expected format
+     */
+    private Font loadFontsByLocale(String language) throws IOException, FontFormatException {
+        String fontName = LANGUAGE_TO_FONT_MAP.getOrDefault(language, DEFAULT_FONT_NAME);
+        String[] suffixes = {".otf", ".ttf", ".ttc"};
+        for (String suffix : suffixes) {
+            File fontFile = new File(mFontDirPath, fontName + suffix);
+            if (fontFile.isFile()) {
+                return Font.createFont(Font.TRUETYPE_FONT, fontFile).deriveFont(mFontSize);
+            }
+        }
+
+        throw new IOException(
+                "Can not find the font file " + fontName + " for language " + language);
+    }
+
+    /** Separates the text string by spaces and wraps it by words. */
+    private List<String> wrapTextByWords(String text, FontMetrics metrics) {
+        List<String> wrappedText = new ArrayList<>();
+        StringTokenizer st = new StringTokenizer(text, " \n");
+
+        // TODO(xunchang). We assume that all words can fit on the screen. Raise an
+        // IllegalStateException if the word is wider than the image width.
+        StringBuilder line = new StringBuilder();
+        while (st.hasMoreTokens()) {
+            String token = st.nextToken();
+            if (metrics.stringWidth(line + token + " ") > mImageWidth) {
+                wrappedText.add(line.toString());
+                line = new StringBuilder();
+            }
+            line.append(token).append(" ");
+        }
         wrappedText.add(line.toString());
-        line = new StringBuilder();
-      }
-      line.append(token).append(" ");
+
+        return wrappedText;
     }
-    wrappedText.add(line.toString());
 
-    return wrappedText;
-  }
+    /** One character is a word for CJK. */
+    private List<String> wrapTextByCharacters(String text, FontMetrics metrics) {
+        List<String> wrappedText = new ArrayList<>();
 
-  /**
-   * One character is a word for CJK.
-   */
-  private List<String> wrapTextByCharacters(String text, FontMetrics metrics) {
-    List<String> wrappedText = new ArrayList<>();
-
-    StringBuilder line = new StringBuilder();
-    for (char token : text.toCharArray()) {
-      if (metrics.stringWidth(line + Character.toString(token)) > mImageWidth) {
+        StringBuilder line = new StringBuilder();
+        for (char token : text.toCharArray()) {
+            if (metrics.stringWidth(line + Character.toString(token)) > mImageWidth) {
+                wrappedText.add(line.toString());
+                line = new StringBuilder();
+            }
+            line.append(token);
+        }
         wrappedText.add(line.toString());
-        line = new StringBuilder();
-      }
-      line.append(token);
-    }
-    wrappedText.add(line.toString());
 
-    return wrappedText;
-  }
-
-  /**
-   * Wraps the text with a maximum of mImageWidth pixels per line.
-   *
-   * @param text the string representation of text to wrap
-   * @param metrics the metrics of the Font used to draw the text; it gives the width in pixels of
-   *    the text given its string representation
-   *
-   * @return a list of strings with their width smaller than mImageWidth pixels
-   */
-  private List<String> wrapText(String text, FontMetrics metrics, String language) {
-    if (LOGOGRAM_LANGUAGE.contains(language)) {
-      return wrapTextByCharacters(text, metrics);
+        return wrappedText;
     }
 
-    return wrapTextByWords(text, metrics);
-  }
+    /**
+     * Wraps the text with a maximum of mImageWidth pixels per line.
+     *
+     * @param text the string representation of text to wrap
+     * @param metrics the metrics of the Font used to draw the text; it gives the width in pixels of
+     *     the text given its string representation
+     * @return a list of strings with their width smaller than mImageWidth pixels
+     */
+    private List<String> wrapText(String text, FontMetrics metrics, String language) {
+        if (LOGOGRAM_LANGUAGE.contains(language)) {
+            return wrapTextByCharacters(text, metrics);
+        }
 
-  /**
-   * Encodes the information of the text image for |locale|.
-   * According to minui/resources.cpp, the width, height and locale of the image is decoded as:
-   *   int w = (row[1] << 8) | row[0];
-   *   int h = (row[3] << 8) | row[2];
-   *   __unused int len = row[4];
-   *   char* loc = reinterpret_cast<char*>(&row[5]);
-  */
-  private List<Integer> encodeTextInfo(int width, int height, String locale) {
-    List<Integer> info = new ArrayList<>(Arrays.asList(width & 0xff, width >> 8,
-        height & 0xff, height >> 8, locale.length()));
-
-    byte[] localeBytes = locale.getBytes();
-    for (byte b: localeBytes) {
-      info.add((int)b);
-    }
-    info.add(0);
-
-    return info;
-  }
-
-  /**
-   * Draws the text string on the canvas for given locale.
-   *
-   * @param text the string to draw on canvas
-   * @param locale the current locale tag of the string to draw
-   *
-   * @throws IOException if we cannot find the corresponding font file for the given locale.
-   * @throws FontFormatException if we failed to load the font file for the given locale.
-   */
-  private void drawText(String text, Locale locale, String languageTag, boolean centralAlignment)
-      throws IOException, FontFormatException  {
-    Graphics2D graphics = mBufferedImage.createGraphics();
-    graphics.setColor(Color.WHITE);
-    graphics.setRenderingHint(
-        RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
-    graphics.setFont(loadFontsByLocale(locale.getLanguage()));
-
-    System.out.println("Encoding \"" + locale + "\" as \"" + languageTag + "\": " + text);
-
-    FontMetrics fontMetrics = graphics.getFontMetrics();
-    List<String> wrappedText = wrapText(text, fontMetrics, locale.getLanguage());
-
-    // Marks the start y offset for the text image of current locale; and reserves one line to
-    // encode the image metadata.
-    int currentImageStart = mVerticalOffset;
-    mVerticalOffset += 1;
-    for (String line : wrappedText) {
-      int lineHeight = fontMetrics.getHeight();
-      // Doubles the height of the image if we are short of space.
-      if (mVerticalOffset + lineHeight >= mImageHeight) {
-        resizeHeight(mImageHeight * 2);
-      }
-
-      // Draws the text at mVerticalOffset and increments the offset with line space.
-      int baseLine = mVerticalOffset + lineHeight - fontMetrics.getDescent();
-
-      // Draws from right if it's an RTL language.
-      int x = centralAlignment ? (mImageWidth - fontMetrics.stringWidth(line)) / 2 :
-          RTL_LANGUAGE.contains(languageTag) ? mImageWidth - fontMetrics.stringWidth(line) : 0;
-
-      graphics.drawString(line, x, baseLine);
-
-      mVerticalOffset += lineHeight;
+        return wrapTextByWords(text, metrics);
     }
 
-    // Encodes the metadata of the current localized image as pixels.
-    int currentImageHeight = mVerticalOffset - currentImageStart - 1;
-    List<Integer> info = encodeTextInfo(mImageWidth, currentImageHeight, languageTag);
-    for (int i = 0; i < info.size(); i++) {
-      int pixel[] =  { info.get(i) };
-      mBufferedImage.getRaster().setPixel(i, currentImageStart, pixel);
-    }
-  }
+    /**
+     * Encodes the information of the text image for |locale|. According to minui/resources.cpp, the
+     * width, height and locale of the image is decoded as: int w = (row[1] << 8) | row[0]; int h =
+     * (row[3] << 8) | row[2]; __unused int len = row[4]; char* loc =
+     * reinterpret_cast<char*>(&row[5]);
+     */
+    private List<Integer> encodeTextInfo(int width, int height, String locale) {
+        List<Integer> info =
+                new ArrayList<>(
+                        Arrays.asList(
+                                width & 0xff,
+                                width >> 8,
+                                height & 0xff,
+                                height >> 8,
+                                locale.length()));
 
-  /**
-   * Redraws the image with the new height.
-   *
-   * @param height the new height of the image in pixels.
-   */
-  private void resizeHeight(int height) {
-    BufferedImage resizedImage =
-        new BufferedImage(mImageWidth, height, BufferedImage.TYPE_BYTE_GRAY);
-    Graphics2D graphic = resizedImage.createGraphics();
-    graphic.drawImage(mBufferedImage, 0, 0, null);
-    graphic.dispose();
+        byte[] localeBytes = locale.getBytes();
+        for (byte b : localeBytes) {
+            info.add((int) b);
+        }
+        info.add(0);
 
-    mBufferedImage = resizedImage;
-    mImageHeight = height;
-  }
-
-  /**
-   *  This function draws the font characters and saves the result to outputPath.
-   *
-   * @param localizedTextMap a map from locale to its translated text string
-   * @param outputPath the path to write the generated image file.
-   *
-   * @throws FontFormatException if there's a format error in one of the font file
-   * @throws IOException if we cannot find the font file for one of the locale, or we failed to
-   *    write the image file.
-   */
-  public void generateImage(Map<Locale, String> localizedTextMap, String outputPath) throws
-      FontFormatException, IOException {
-    Map<String, Integer> languageCount = new TreeMap<>();
-    for (Locale locale : localizedTextMap.keySet()) {
-      String language = locale.getLanguage();
-      languageCount.put(language, languageCount.getOrDefault(language, 0) + 1 );
+        return info;
     }
 
-    for (Locale locale : localizedTextMap.keySet()) {
-      Integer count = languageCount.get(locale.getLanguage());
-      // Recovery expects en-US instead of en_US.
-      String languageTag = locale.toLanguageTag();
-      if (count == 1) {
-        // Make the last country variant for a given language be the catch-all for that language.
-        languageTag = locale.getLanguage();
-      } else {
-        languageCount.put(locale.getLanguage(), count - 1);
-      }
+    /**
+     * Draws the text string on the canvas for given locale.
+     *
+     * @param text the string to draw on canvas
+     * @param locale the current locale tag of the string to draw
+     * @throws IOException if we cannot find the corresponding font file for the given locale.
+     * @throws FontFormatException if we failed to load the font file for the given locale.
+     */
+    private void drawText(String text, Locale locale, String languageTag, boolean centralAlignment)
+            throws IOException, FontFormatException {
+        Graphics2D graphics = mBufferedImage.createGraphics();
+        graphics.setColor(Color.WHITE);
+        graphics.setRenderingHint(
+                RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
+        graphics.setFont(loadFontsByLocale(locale.getLanguage()));
 
-      drawText(localizedTextMap.get(locale), locale, languageTag, false);
+        System.out.println("Encoding \"" + locale + "\" as \"" + languageTag + "\": " + text);
+
+        FontMetrics fontMetrics = graphics.getFontMetrics();
+        List<String> wrappedText = wrapText(text, fontMetrics, locale.getLanguage());
+
+        // Marks the start y offset for the text image of current locale; and reserves one line to
+        // encode the image metadata.
+        int currentImageStart = mVerticalOffset;
+        mVerticalOffset += 1;
+        for (String line : wrappedText) {
+            int lineHeight = fontMetrics.getHeight();
+            // Doubles the height of the image if we are short of space.
+            if (mVerticalOffset + lineHeight >= mImageHeight) {
+                resizeHeight(mImageHeight * 2);
+            }
+
+            // Draws the text at mVerticalOffset and increments the offset with line space.
+            int baseLine = mVerticalOffset + lineHeight - fontMetrics.getDescent();
+
+            // Draws from right if it's an RTL language.
+            int x =
+                    centralAlignment
+                            ? (mImageWidth - fontMetrics.stringWidth(line)) / 2
+                            : RTL_LANGUAGE.contains(languageTag)
+                                    ? mImageWidth - fontMetrics.stringWidth(line)
+                                    : 0;
+
+            graphics.drawString(line, x, baseLine);
+
+            mVerticalOffset += lineHeight;
+        }
+
+        // Encodes the metadata of the current localized image as pixels.
+        int currentImageHeight = mVerticalOffset - currentImageStart - 1;
+        List<Integer> info = encodeTextInfo(mImageWidth, currentImageHeight, languageTag);
+        for (int i = 0; i < info.size(); i++) {
+            int[] pixel = {info.get(i)};
+            mBufferedImage.getRaster().setPixel(i, currentImageStart, pixel);
+        }
     }
 
-    // TODO(xunchang) adjust the width to save some space if all texts are smaller than imageWidth.
-    resizeHeight(mVerticalOffset);
-    ImageIO.write(mBufferedImage, "png", new File(outputPath));
-  }
+    /**
+     * Redraws the image with the new height.
+     *
+     * @param height the new height of the image in pixels.
+     */
+    private void resizeHeight(int height) {
+        BufferedImage resizedImage =
+                new BufferedImage(mImageWidth, height, BufferedImage.TYPE_BYTE_GRAY);
+        Graphics2D graphic = resizedImage.createGraphics();
+        graphic.drawImage(mBufferedImage, 0, 0, null);
+        graphic.dispose();
 
-  public static void printUsage(Options options) {
-    new HelpFormatter().printHelp("java -jar path_to_jar [required_options]", options);
-
-  }
-
-  public static Options createOptions() {
-    Options options = new Options();
-    options.addOption(OptionBuilder
-        .withLongOpt("image_width")
-        .withDescription("The initial width of the image in pixels.")
-        .hasArgs(1)
-        .isRequired()
-        .create());
-
-    options.addOption(OptionBuilder
-        .withLongOpt("text_name")
-        .withDescription("The description of the text string, e.g. recovery_erasing")
-        .hasArgs(1)
-        .isRequired()
-        .create());
-
-    options.addOption(OptionBuilder
-        .withLongOpt("font_dir")
-        .withDescription("The directory that contains all the support font format files, e.g."
-            + " $OUT/system/fonts/")
-        .hasArgs(1)
-        .isRequired()
-        .create());
-
-    options.addOption(OptionBuilder
-        .withLongOpt("resource_dir")
-        .withDescription("The resource directory that contains all the translated strings in xml"
-            + " format, e.g. bootable/recovery/tools/recovery_l10n/res/")
-        .hasArgs(1)
-        .isRequired()
-        .create());
-
-    options.addOption(OptionBuilder
-        .withLongOpt("output_file")
-        .withDescription("Path to the generated image")
-        .hasArgs(1)
-        .isRequired()
-        .create());
-
-    return options;
-  }
-
-  public static void main(String[] args) throws NumberFormatException, IOException,
-      FontFormatException, LocalizedStringNotFoundException {
-    Options options = createOptions();
-    CommandLine cmd;
-    try {
-      cmd = new GnuParser().parse(options, args);
-    } catch (ParseException e) {
-      System.err.println(e.getMessage());
-      printUsage(options);
-      return;
+        mBufferedImage = resizedImage;
+        mImageHeight = height;
     }
 
-    int imageWidth = Integer.parseUnsignedInt(cmd.getOptionValue("image_width"));
+    /**
+     * This function draws the font characters and saves the result to outputPath.
+     *
+     * @param localizedTextMap a map from locale to its translated text string
+     * @param outputPath the path to write the generated image file.
+     * @throws FontFormatException if there's a format error in one of the font file
+     * @throws IOException if we cannot find the font file for one of the locale, or we failed to
+     *     write the image file.
+     */
+    public void generateImage(Map<Locale, String> localizedTextMap, String outputPath)
+            throws FontFormatException, IOException {
+        Map<String, Integer> languageCount = new TreeMap<>();
+        for (Locale locale : localizedTextMap.keySet()) {
+            String language = locale.getLanguage();
+            languageCount.put(language, languageCount.getOrDefault(language, 0) + 1);
+        }
 
-    ImageGenerator imageGenerator = new ImageGenerator(imageWidth, cmd.getOptionValue("text_name"),
-        DEFAULT_FONT_SIZE, cmd.getOptionValue("font_dir"));
+        for (Locale locale : localizedTextMap.keySet()) {
+            Integer count = languageCount.get(locale.getLanguage());
+            // Recovery expects en-US instead of en_US.
+            String languageTag = locale.toLanguageTag();
+            if (count == 1) {
+                // Make the last country variant for a given language be the catch-all for that
+                // language.
+                languageTag = locale.getLanguage();
+            } else {
+                languageCount.put(locale.getLanguage(), count - 1);
+            }
 
-    Map<Locale, String> localizedStringMap =
-        imageGenerator.readLocalizedStringFromXmls(cmd.getOptionValue("resource_dir"));
-    imageGenerator.generateImage(localizedStringMap, cmd.getOptionValue("output_file"));
-  }
+            drawText(localizedTextMap.get(locale), locale, languageTag, false);
+        }
+
+        // TODO(xunchang) adjust the width to save some space if all texts are smaller than
+        // imageWidth.
+        resizeHeight(mVerticalOffset);
+        ImageIO.write(mBufferedImage, "png", new File(outputPath));
+    }
+
+    /** Prints the helper message. */
+    public static void printUsage(Options options) {
+        new HelpFormatter().printHelp("java -jar path_to_jar [required_options]", options);
+    }
+
+    /** Creates the command line options. */
+    public static Options createOptions() {
+        Options options = new Options();
+        options.addOption(
+                OptionBuilder.withLongOpt("image_width")
+                        .withDescription("The initial width of the image in pixels.")
+                        .hasArgs(1)
+                        .isRequired()
+                        .create());
+
+        options.addOption(
+                OptionBuilder.withLongOpt("text_name")
+                        .withDescription(
+                                "The description of the text string, e.g. recovery_erasing")
+                        .hasArgs(1)
+                        .isRequired()
+                        .create());
+
+        options.addOption(
+                OptionBuilder.withLongOpt("font_dir")
+                        .withDescription(
+                                "The directory that contains all the support font format files, "
+                                        + "e.g. $OUT/system/fonts/")
+                        .hasArgs(1)
+                        .isRequired()
+                        .create());
+
+        options.addOption(
+                OptionBuilder.withLongOpt("resource_dir")
+                        .withDescription(
+                                "The resource directory that contains all the translated strings in"
+                                        + " xml format, e.g."
+                                        + " bootable/recovery/tools/recovery_l10n/res/")
+                        .hasArgs(1)
+                        .isRequired()
+                        .create());
+
+        options.addOption(
+                OptionBuilder.withLongOpt("output_file")
+                        .withDescription("Path to the generated image")
+                        .hasArgs(1)
+                        .isRequired()
+                        .create());
+
+        return options;
+    }
+
+    /** The main function parses the command line options and generates the desired text image. */
+    public static void main(String[] args)
+            throws NumberFormatException, IOException, FontFormatException,
+                    LocalizedStringNotFoundException {
+        Options options = createOptions();
+        CommandLine cmd;
+        try {
+            cmd = new GnuParser().parse(options, args);
+        } catch (ParseException e) {
+            System.err.println(e.getMessage());
+            printUsage(options);
+            return;
+        }
+
+        int imageWidth = Integer.parseUnsignedInt(cmd.getOptionValue("image_width"));
+
+        ImageGenerator imageGenerator =
+                new ImageGenerator(
+                        imageWidth,
+                        cmd.getOptionValue("text_name"),
+                        DEFAULT_FONT_SIZE,
+                        cmd.getOptionValue("font_dir"));
+
+        Map<Locale, String> localizedStringMap =
+                imageGenerator.readLocalizedStringFromXmls(cmd.getOptionValue("resource_dir"));
+        imageGenerator.generateImage(localizedStringMap, cmd.getOptionValue("output_file"));
+    }
 }