From 2a97bbd1ea9db1975364b6ee96e527b2c9c8bae7 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Thu, 10 Apr 2014 07:58:32 +0200 Subject: Use the Android-provided API to stream-decode base64-encoded data --- main/src/cgeo/geocaching/network/HtmlImage.java | 23 ++------------ main/src/cgeo/geocaching/utils/ImageUtils.java | 39 ++++++++++++++++++++++++ tests/res/raw/small_file.bin | Bin 0 -> 409 bytes tests/src/cgeo/geocaching/ImageUtilsTest.java | 30 ++++++++++++++++++ 4 files changed, 72 insertions(+), 20 deletions(-) create mode 100644 tests/res/raw/small_file.bin create mode 100644 tests/src/cgeo/geocaching/ImageUtilsTest.java diff --git a/main/src/cgeo/geocaching/network/HtmlImage.java b/main/src/cgeo/geocaching/network/HtmlImage.java index bde85ee..8ee2ba7 100644 --- a/main/src/cgeo/geocaching/network/HtmlImage.java +++ b/main/src/cgeo/geocaching/network/HtmlImage.java @@ -13,7 +13,6 @@ import cgeo.geocaching.utils.Log; import cgeo.geocaching.utils.RxUtils; import ch.boye.httpclientandroidlib.HttpResponse; -import ch.boye.httpclientandroidlib.androidextra.Base64; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; @@ -43,9 +42,6 @@ import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; import java.util.Date; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; @@ -172,14 +168,15 @@ public class HtmlImage implements Html.ImageGetter { return new ImmutablePair(bitmap != null ? ImageUtils.scaleBitmapToFitDisplay(bitmap) : null, - loadResult.getRight()); + loadResult.getRight() + ); } private void downloadAndSave(final Subscriber subscriber) { final File file = LocalStorage.getStorageFile(pseudoGeocode, url, true, true); if (url.startsWith("data:image/")) { if (url.contains(";base64,")) { - saveBase64ToFile(url, file); + ImageUtils.decodeBase64ToFile(StringUtils.substringAfter(url, ";base64,"), file); } else { Log.e("HtmlImage.getDrawable: unable to decode non-base64 inline image"); subscriber.onCompleted(); @@ -254,20 +251,6 @@ public class HtmlImage implements Html.ImageGetter { return false; } - private static void saveBase64ToFile(final String url, final File file) { - // TODO: when we use SDK level 8 or above, we can use the streaming version of the base64 - // Android utilities. - OutputStream out = null; - try { - out = new FileOutputStream(file); - out.write(Base64.decode(StringUtils.substringAfter(url, ";base64,"), Base64.DEFAULT)); - } catch (final IOException e) { - Log.e("HtmlImage.saveBase64ToFile: cannot write file for decoded inline image", e); - } finally { - IOUtils.closeQuietly(out); - } - } - /** * Make a fresh copy of the file to reset its timestamp. On some storage, it is impossible * to modify the modified time after the fact, in which case a brand new file must be diff --git a/main/src/cgeo/geocaching/utils/ImageUtils.java b/main/src/cgeo/geocaching/utils/ImageUtils.java index aae0f14..6d7a437 100644 --- a/main/src/cgeo/geocaching/utils/ImageUtils.java +++ b/main/src/cgeo/geocaching/utils/ImageUtils.java @@ -3,6 +3,7 @@ package cgeo.geocaching.utils; import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.compatibility.Compatibility; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; @@ -16,11 +17,15 @@ import android.graphics.drawable.BitmapDrawable; import android.media.ExifInterface; import android.net.Uri; import android.os.Environment; +import android.util.Base64; +import android.util.Base64InputStream; import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; @@ -245,4 +250,38 @@ public final class ImageUtils { } return false; } + + /** + * Decode a base64-encoded string and save the result into a file. + * + * @param inString the encoded string + * @param outFile the file to save the decoded result into + */ + public static void decodeBase64ToFile(final String inString, final File outFile) { + FileOutputStream out = null; + try { + out = new FileOutputStream(outFile); + decodeBase64ToStream(inString, out); + } catch (final IOException e) { + Log.e("HtmlImage.decodeBase64ToFile: cannot write file for decoded inline image", e); + } finally { + IOUtils.closeQuietly(out); + } + } + + /** + * Decode a base64-encoded string and save the result into a stream. + * + * @param inString the encoded string + * @param outFile the file to save the decoded result into + */ + public static void decodeBase64ToStream(final String inString, final OutputStream out) throws IOException { + Base64InputStream in = null; + try { + in = new Base64InputStream(new ByteArrayInputStream(inString.getBytes()), Base64.DEFAULT); + IOUtils.copy(in, out); + } finally { + IOUtils.closeQuietly(in); + } + } } diff --git a/tests/res/raw/small_file.bin b/tests/res/raw/small_file.bin new file mode 100644 index 0000000..df5b053 Binary files /dev/null and b/tests/res/raw/small_file.bin differ diff --git a/tests/src/cgeo/geocaching/ImageUtilsTest.java b/tests/src/cgeo/geocaching/ImageUtilsTest.java new file mode 100644 index 0000000..c67d340 --- /dev/null +++ b/tests/src/cgeo/geocaching/ImageUtilsTest.java @@ -0,0 +1,30 @@ +package cgeo.geocaching; + +import cgeo.geocaching.test.AbstractResourceInstrumentationTestCase; +import cgeo.geocaching.test.R; +import cgeo.geocaching.utils.ImageUtils; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; + +public class ImageUtilsTest extends AbstractResourceInstrumentationTestCase { + + private static final String icon64 = "iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAABGdBTUEAALGPC/xhBQAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAABAElEQVQY002NvUpDQRQG52yEILn+IAYCClaS4tqKtZ1dbHwNuaS00dYmQmrfwMZGRBAtrMQHSCMpTCMqaIx6k909e6wCme4bPhhhhuc8d8PxeC+ZPW33++9T72ZPvdFow1SvStX9We8sz2U6FlQPLUYqqkW30VgGsKJAbur1g5pzXYMosC5giEgy+6hAtUzpqLLq3O8q7M6bbZkqmpKllExUa9+q2gvhbJrKLrLsdkVkxwABShg9eN86nUzunXU6AD/O+2EMgdJ7fAiY92EtxjcAJ+02JyKNkNLmawj9xxiLlxAu/2JcWoQmwBxAFT4Hqq1rs687GADnx9DMnOsD/AMJ54Nj8e9zcgAAAABJRU5ErkJggg=="; + + public void testBase64decoding() throws IOException { + final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ImageUtils.decodeBase64ToStream(icon64, outputStream); + final byte[] decodedImage = outputStream.toByteArray(); + outputStream.close(); + assertEquals("decoded image has the right size", 409, decodedImage.length); + final InputStream originalStream = getResourceStream(R.raw.small_file); + final byte[] originalImage = new byte[409]; + assertEquals("original image has the right size (consistency check)", 409, originalStream.read(originalImage)); + originalStream.close(); + assertTrue("decoded base64 image is similar to original file data", + Arrays.equals(originalImage, decodedImage)); + } + +} -- cgit v1.1