diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2018-11-06 05:09:03 +0100 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2018-11-06 05:09:03 +0100 |
commit | 104b7a3e629b4c0a4a8d18de44efc4ce5927c4ab (patch) | |
tree | bd635f319762c5455faae5983ac20bb01b5cef87 | |
parent | Snap for 5110273 from 9b21c7df089bb0c2a82617726f84f2054d5a40ae to qt-release (diff) | |
parent | Merge "updater: Error out on underrun during patching." am: a0404ecd16 am: 2a4e64a351 (diff) | |
download | android_bootable_recovery-104b7a3e629b4c0a4a8d18de44efc4ce5927c4ab.tar android_bootable_recovery-104b7a3e629b4c0a4a8d18de44efc4ce5927c4ab.tar.gz android_bootable_recovery-104b7a3e629b4c0a4a8d18de44efc4ce5927c4ab.tar.bz2 android_bootable_recovery-104b7a3e629b4c0a4a8d18de44efc4ce5927c4ab.tar.lz android_bootable_recovery-104b7a3e629b4c0a4a8d18de44efc4ce5927c4ab.tar.xz android_bootable_recovery-104b7a3e629b4c0a4a8d18de44efc4ce5927c4ab.tar.zst android_bootable_recovery-104b7a3e629b4c0a4a8d18de44efc4ce5927c4ab.zip |
-rw-r--r-- | tests/component/updater_test.cpp | 42 | ||||
-rw-r--r-- | tests/testdata/jarsigned.zip | bin | 2271 -> 0 bytes | |||
-rw-r--r-- | tests/testdata/patch.bsdiff | bin | 57476 -> 0 bytes | |||
-rw-r--r-- | tests/testdata/unsigned.zip | bin | 376 -> 0 bytes | |||
-rw-r--r-- | tools/image_generator/ImageGenerator.java | 137 | ||||
-rw-r--r-- | updater/blockimg.cpp | 5 |
6 files changed, 169 insertions, 15 deletions
diff --git a/tests/component/updater_test.cpp b/tests/component/updater_test.cpp index 24c63e776..c611c2291 100644 --- a/tests/component/updater_test.cpp +++ b/tests/component/updater_test.cpp @@ -664,6 +664,48 @@ TEST_F(UpdaterTest, block_image_update_patch_data) { ASSERT_EQ(tgt_content, updated_content); } +TEST_F(UpdaterTest, block_image_update_patch_underrun) { + std::string src_content = std::string(4096, 'a') + std::string(4096, 'c'); + std::string tgt_content = std::string(4096, 'b') + std::string(4096, 'd'); + + // Generate the patch data. We intentionally provide one-byte short target to trigger the underrun + // path. + TemporaryFile patch_file; + ASSERT_EQ(0, + bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(src_content.data()), src_content.size(), + reinterpret_cast<const uint8_t*>(tgt_content.data()), + tgt_content.size() - 1, patch_file.path, nullptr)); + std::string patch_content; + ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch_content)); + + // Create the transfer list that contains a bsdiff. + std::string src_hash = get_sha1(src_content); + std::string tgt_hash = get_sha1(tgt_content); + std::vector<std::string> transfer_list{ + // clang-format off + "4", + "2", + "0", + "2", + "stash " + src_hash + " 2,0,2", + android::base::StringPrintf("bsdiff 0 %zu %s %s 2,0,2 2 - %s:2,0,2", patch_content.size(), + src_hash.c_str(), tgt_hash.c_str(), src_hash.c_str()), + "free " + src_hash, + // clang-format on + }; + + PackageEntries entries{ + { "new_data", "" }, + { "patch_data", patch_content }, + { "transfer_list", android::base::Join(transfer_list, '\n') }, + }; + + ASSERT_TRUE(android::base::WriteStringToFile(src_content, image_file_)); + + // The update should fail due to underrun. + RunBlockImageUpdate(false, entries, image_file_, "", kPatchApplicationFailure); +} + TEST_F(UpdaterTest, block_image_update_fail) { std::string src_content(4096 * 2, 'e'); std::string src_hash = get_sha1(src_content); diff --git a/tests/testdata/jarsigned.zip b/tests/testdata/jarsigned.zip Binary files differdeleted file mode 100644 index 8b1ef8bdd..000000000 --- a/tests/testdata/jarsigned.zip +++ /dev/null diff --git a/tests/testdata/patch.bsdiff b/tests/testdata/patch.bsdiff Binary files differdeleted file mode 100644 index b78d38573..000000000 --- a/tests/testdata/patch.bsdiff +++ /dev/null diff --git a/tests/testdata/unsigned.zip b/tests/testdata/unsigned.zip Binary files differdeleted file mode 100644 index 24e3eadac..000000000 --- a/tests/testdata/unsigned.zip +++ /dev/null diff --git a/tools/image_generator/ImageGenerator.java b/tools/image_generator/ImageGenerator.java index b9103379e..8ed696a45 100644 --- a/tools/image_generator/ImageGenerator.java +++ b/tools/image_generator/ImageGenerator.java @@ -26,10 +26,12 @@ import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.ArrayList; -import java.util.Comparator; +import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; import java.util.TreeMap; import java.util.StringTokenizer; @@ -115,6 +117,25 @@ public class ImageGenerator { 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 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. */ @@ -220,7 +241,20 @@ public class ImageGenerator { } Map<Locale, String> result = - new TreeMap<Locale, String>(Comparator.comparing(Locale::toLanguageTag)); + // 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( @@ -269,6 +303,8 @@ public class ImageGenerator { 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(); @@ -284,6 +320,25 @@ public class ImageGenerator { } /** + * 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) { + 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 @@ -292,15 +347,36 @@ public class ImageGenerator { * * @return a list of strings with their width smaller than mImageWidth pixels */ - private List<String> wrapText(String text, FontMetrics metrics) { - // TODO(xunchang) handle other cases of text wrapping - // 1. RTL languages: "ar"(Arabic), "fa"(Persian), "he"(Hebrew), "iw"(Hebrew), "ur"(Urdu) - // 2. Language uses characters: CJK, "lo"(lao), "km"(khmer) + private List<String> wrapText(String text, FontMetrics metrics, String language) { + if (LOGOGRAM_LANGUAGE.contains(language)) { + return wrapTextByCharacters(text, metrics); + } return wrapTextByWords(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 @@ -309,17 +385,23 @@ public class ImageGenerator { * @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) throws IOException, FontFormatException { + 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("Drawing text for locale " + locale + " text " + text); + System.out.println("Encoding \"" + locale + "\" as \"" + languageTag + "\": " + text); FontMetrics fontMetrics = graphics.getFontMetrics(); - List<String> wrappedText = wrapTextByWords(text, fontMetrics); + 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. @@ -329,9 +411,23 @@ public class ImageGenerator { // Draws the text at mVerticalOffset and increments the offset with line space. int baseLine = mVerticalOffset + lineHeight - fontMetrics.getDescent(); - graphics.drawString(line, 0, baseLine); + + // 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); + } } /** @@ -362,11 +458,24 @@ public class ImageGenerator { */ 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 ); + } + for (Locale locale : localizedTextMap.keySet()) { - // TODO(xunchang) reprocess the locales for the same language and make the last locale the - // catch-all type. e.g. "zh-CN, zh-HK, zh-TW" will become "zh-CN, zh-HK, zh" - // Or maybe we don't need to support these variants? - drawText(localizedTextMap.get(locale), locale); + 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); + } + + drawText(localizedTextMap.get(locale), locale, languageTag, false); } // TODO(xunchang) adjust the width to save some space if all texts are smaller than imageWidth. diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp index 47849a155..c4c09098e 100644 --- a/updater/blockimg.cpp +++ b/updater/blockimg.cpp @@ -1399,7 +1399,10 @@ static int PerformCommandDiff(CommandParameters& params) { // We expect the output of the patcher to fill the tgt ranges exactly. if (!writer.Finished()) { - LOG(ERROR) << "range sink underrun?"; + LOG(ERROR) << "Failed to fully write target blocks (range sink underrun): Missing " + << writer.AvailableSpace() << " bytes"; + failure_type = kPatchApplicationFailure; + return -1; } } else { LOG(INFO) << "skipping " << blocks << " blocks already patched to " << tgt.blocks() << " [" |