/* * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.imagepipeline.platform; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Build; import android.os.MemoryFile; import javax.annotation.Nullable; import com.facebook.common.internal.ByteStreams; import com.facebook.common.internal.Closeables; import com.facebook.common.internal.Preconditions; import com.facebook.common.internal.Throwables; import com.facebook.common.references.CloseableReference; import com.facebook.common.streams.LimitedInputStream; import com.facebook.imagepipeline.image.EncodedImage; import com.facebook.imagepipeline.memory.FlexByteArrayPool; import com.facebook.imagepipeline.memory.PooledByteBuffer; import com.facebook.imagepipeline.memory.PooledByteBufferInputStream; import com.facebook.imagepipeline.nativecode.Bitmaps; import com.facebook.imageutils.JfifUtil; import java.io.FileDescriptor; import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.Method; /** * Bitmap decoder (Gingerbread to Jelly Bean). * * <p>This copies incoming encoded bytes into a MemoryFile, and then decodes them using a file * descriptor, thus avoiding using any Java memory at all. This technique only works in JellyBean * and below. */ public class GingerbreadPurgeableDecoder extends DalvikPurgeableDecoder { private static Method sGetFileDescriptorMethod; /** * Decodes a byteArray into a purgeable bitmap * * @param bytesRef the byte buffer that contains the encoded bytes * @param options the options passed to the BitmapFactory * @return */ @Override protected Bitmap decodeByteArrayAsPurgeable( CloseableReference<PooledByteBuffer> bytesRef, BitmapFactory.Options options) { return decodeFileDescriptorAsPurgeable(bytesRef, bytesRef.get().size(), null, options); } /** * Decodes a byteArray containing jpeg encoded bytes into a purgeable bitmap * * <p> Adds a JFIF End-Of-Image marker if needed before decoding. * * @param bytesRef the byte buffer that contains the encoded bytes * @param length the length of bytes for decox * @param options the options passed to the BitmapFactory * @return */ @Override protected Bitmap decodeJPEGByteArrayAsPurgeable( CloseableReference<PooledByteBuffer> bytesRef, int length, BitmapFactory.Options options) { byte[] suffix = endsWithEOI(bytesRef, length) ? null : EOI; return decodeFileDescriptorAsPurgeable(bytesRef, length, suffix, options); } private static MemoryFile copyToMemoryFile( CloseableReference<PooledByteBuffer> bytesRef, int inputLength, @Nullable byte[] suffix) throws IOException { int outputLength = inputLength + (suffix == null ? 0 : suffix.length); MemoryFile memoryFile = new MemoryFile(null, outputLength); memoryFile.allowPurging(false); PooledByteBufferInputStream pbbIs = null; LimitedInputStream is = null; OutputStream os = null; try { pbbIs = new PooledByteBufferInputStream(bytesRef.get()); is = new LimitedInputStream(pbbIs, inputLength); os = memoryFile.getOutputStream(); ByteStreams.copy(is, os); if (suffix != null) { memoryFile.writeBytes(suffix, 0, inputLength, suffix.length); } return memoryFile; } finally { CloseableReference.closeSafely(bytesRef); Closeables.closeQuietly(pbbIs); Closeables.closeQuietly(is); Closeables.close(os, true); } } private synchronized Method getFileDescriptorMethod() { if (sGetFileDescriptorMethod == null) { try { sGetFileDescriptorMethod = MemoryFile.class.getDeclaredMethod("getFileDescriptor"); } catch (Exception e) { throw Throwables.propagate(e); } } return sGetFileDescriptorMethod; } private FileDescriptor getMemoryFileDescriptor(MemoryFile memoryFile) { try { Object rawFD = getFileDescriptorMethod().invoke(memoryFile); return (FileDescriptor) rawFD; } catch (Exception e) { throw Throwables.propagate(e); } } protected Bitmap decodeFileDescriptorAsPurgeable( CloseableReference<PooledByteBuffer> bytesRef, int inputLength, byte[] suffix, BitmapFactory.Options options) { MemoryFile memoryFile = null; try { memoryFile = copyToMemoryFile(bytesRef, inputLength, suffix); FileDescriptor fd = getMemoryFileDescriptor(memoryFile); Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fd, null, options); return Preconditions.checkNotNull(bitmap, "BitmapFactory returned null"); } catch (IOException e) { throw Throwables.propagate(e); } finally { if (memoryFile != null) { memoryFile.close(); } } } }