Move Bitmap to AParcel
Bug: 145227478
Test: device boots; android.graphics.cts.BitmapTest passes
Change-Id: I760fa5155549e92c625a28cdc847c8d7b0b178b3
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index ba66905..084bdaa 100755
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -19,12 +19,19 @@
#include <utils/Color.h>
#ifdef __ANDROID__ // Layoutlib does not support graphic buffer, parcel or render thread
+#include <android-base/unique_fd.h>
+#include <android/binder_parcel.h>
+#include <android/binder_parcel_jni.h>
+#include <android/binder_parcel_platform.h>
+#include <android/binder_parcel_utils.h>
#include <private/android/AHardwareBufferHelpers.h>
-#include <binder/Parcel.h>
+#include <cutils/ashmem.h>
#include <dlfcn.h>
#include <renderthread/RenderProxy.h>
+#include <sys/mman.h>
#endif
+#include <inttypes.h>
#include <string.h>
#include <memory>
#include <string>
@@ -567,149 +574,289 @@
///////////////////////////////////////////////////////////////////////////////
+// TODO: Move somewhere else
#ifdef __ANDROID__ // Layoutlib does not support parcel
-static struct parcel_offsets_t
-{
- jclass clazz;
- jfieldID mNativePtr;
-} gParcelOffsets;
-static Parcel* parcelForJavaObject(JNIEnv* env, jobject obj) {
- if (obj) {
- Parcel* p = (Parcel*)env->GetLongField(obj, gParcelOffsets.mNativePtr);
- if (p != NULL) {
- return p;
- }
- jniThrowException(env, "java/lang/IllegalStateException", "Parcel has been finalized!");
+class ScopedParcel {
+public:
+ explicit ScopedParcel(JNIEnv* env, jobject parcel) {
+ mParcel = AParcel_fromJavaParcel(env, parcel);
}
- return NULL;
+
+ ~ScopedParcel() { AParcel_delete(mParcel); }
+
+ int32_t readInt32() {
+ int32_t temp = 0;
+ // TODO: This behavior-matches what android::Parcel does
+ // but this should probably be better
+ if (AParcel_readInt32(mParcel, &temp) != STATUS_OK) {
+ temp = 0;
+ }
+ return temp;
+ }
+
+ uint32_t readUint32() {
+ uint32_t temp = 0;
+ // TODO: This behavior-matches what android::Parcel does
+ // but this should probably be better
+ if (AParcel_readUint32(mParcel, &temp) != STATUS_OK) {
+ temp = 0;
+ }
+ return temp;
+ }
+
+ void writeInt32(int32_t value) { AParcel_writeInt32(mParcel, value); }
+
+ void writeUint32(uint32_t value) { AParcel_writeUint32(mParcel, value); }
+
+ bool allowFds() const { return AParcel_getAllowFds(mParcel); }
+
+ std::optional<sk_sp<SkData>> readData() {
+ struct Data {
+ void* ptr = nullptr;
+ size_t size = 0;
+ } data;
+ auto error = AParcel_readByteArray(mParcel, &data,
+ [](void* arrayData, int32_t length,
+ int8_t** outBuffer) -> bool {
+ Data* data = reinterpret_cast<Data*>(arrayData);
+ if (length > 0) {
+ data->ptr = sk_malloc_canfail(length);
+ if (!data->ptr) {
+ return false;
+ }
+ *outBuffer =
+ reinterpret_cast<int8_t*>(data->ptr);
+ data->size = length;
+ }
+ return true;
+ });
+ if (error != STATUS_OK || data.size <= 0) {
+ sk_free(data.ptr);
+ return std::nullopt;
+ } else {
+ return SkData::MakeFromMalloc(data.ptr, data.size);
+ }
+ }
+
+ void writeData(const std::optional<sk_sp<SkData>>& optData) {
+ if (optData) {
+ const auto& data = *optData;
+ AParcel_writeByteArray(mParcel, reinterpret_cast<const int8_t*>(data->data()),
+ data->size());
+ } else {
+ AParcel_writeByteArray(mParcel, nullptr, -1);
+ }
+ }
+
+ AParcel* get() { return mParcel; }
+
+private:
+ AParcel* mParcel;
+};
+
+enum class BlobType : int32_t {
+ IN_PLACE,
+ ASHMEM,
+};
+
+#define ON_ERROR_RETURN(X) \
+ if ((error = (X)) != STATUS_OK) return error
+
+template <typename T, typename U>
+static binder_status_t readBlob(AParcel* parcel, T inPlaceCallback, U ashmemCallback) {
+ binder_status_t error = STATUS_OK;
+ BlobType type;
+ static_assert(sizeof(BlobType) == sizeof(int32_t));
+ ON_ERROR_RETURN(AParcel_readInt32(parcel, (int32_t*)&type));
+ if (type == BlobType::IN_PLACE) {
+ struct Data {
+ std::unique_ptr<int8_t[]> ptr = nullptr;
+ int32_t size = 0;
+ } data;
+ ON_ERROR_RETURN(
+ AParcel_readByteArray(parcel, &data,
+ [](void* arrayData, int32_t length, int8_t** outBuffer) {
+ Data* data = reinterpret_cast<Data*>(arrayData);
+ if (length > 0) {
+ data->ptr = std::make_unique<int8_t[]>(length);
+ data->size = length;
+ *outBuffer = data->ptr.get();
+ }
+ return data->ptr != nullptr;
+ }));
+ inPlaceCallback(std::move(data.ptr), data.size);
+ return STATUS_OK;
+ } else if (type == BlobType::ASHMEM) {
+ int rawFd = -1;
+ int32_t size = 0;
+ ON_ERROR_RETURN(AParcel_readInt32(parcel, &size));
+ ON_ERROR_RETURN(AParcel_readParcelFileDescriptor(parcel, &rawFd));
+ android::base::unique_fd fd(rawFd);
+ ashmemCallback(std::move(fd), size);
+ return STATUS_OK;
+ } else {
+ // Although the above if/else was "exhaustive" guard against unknown types
+ return STATUS_UNKNOWN_ERROR;
+ }
}
-#endif
+
+static constexpr size_t BLOB_INPLACE_LIMIT = 12 * 1024;
+// Fail fast if we can't use ashmem and the size exceeds this limit - the binder transaction
+// wouldn't go through, anyway
+// TODO: Can we get this from somewhere?
+static constexpr size_t BLOB_MAX_INPLACE_LIMIT = 1 * 1024 * 1024;
+static constexpr bool shouldUseAshmem(AParcel* parcel, int32_t size) {
+ return size > BLOB_INPLACE_LIMIT && AParcel_getAllowFds(parcel);
+}
+
+static binder_status_t writeBlobFromFd(AParcel* parcel, int32_t size, int fd) {
+ binder_status_t error = STATUS_OK;
+ ON_ERROR_RETURN(AParcel_writeInt32(parcel, static_cast<int32_t>(BlobType::ASHMEM)));
+ ON_ERROR_RETURN(AParcel_writeInt32(parcel, size));
+ ON_ERROR_RETURN(AParcel_writeParcelFileDescriptor(parcel, fd));
+ return STATUS_OK;
+}
+
+static binder_status_t writeBlob(AParcel* parcel, const int32_t size, const void* data) {
+ if (size <= 0 || data == nullptr) {
+ return STATUS_NOT_ENOUGH_DATA;
+ }
+ binder_status_t error = STATUS_OK;
+ if (shouldUseAshmem(parcel, size)) {
+ // Create new ashmem region with read/write priv
+ base::unique_fd fd(ashmem_create_region("bitmap", size));
+ if (fd.get() < 0) {
+ return STATUS_NO_MEMORY;
+ }
+
+ {
+ void* dest = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0);
+ if (dest == MAP_FAILED) {
+ return STATUS_NO_MEMORY;
+ }
+ memcpy(dest, data, size);
+ munmap(dest, size);
+ }
+
+ if (ashmem_set_prot_region(fd.get(), PROT_READ) < 0) {
+ return STATUS_UNKNOWN_ERROR;
+ }
+ // Workaround b/149851140 in AParcel_writeParcelFileDescriptor
+ int rawFd = fd.release();
+ error = writeBlobFromFd(parcel, size, rawFd);
+ close(rawFd);
+ return error;
+ } else {
+ if (size > BLOB_MAX_INPLACE_LIMIT) {
+ return STATUS_FAILED_TRANSACTION;
+ }
+ ON_ERROR_RETURN(AParcel_writeInt32(parcel, static_cast<int32_t>(BlobType::IN_PLACE)));
+ ON_ERROR_RETURN(AParcel_writeByteArray(parcel, static_cast<const int8_t*>(data), size));
+ return STATUS_OK;
+ }
+}
+
+#undef ON_ERROR_RETURN
+
+#endif // __ANDROID__ // Layoutlib does not support parcel
// This is the maximum possible size because the SkColorSpace must be
// representable (and therefore serializable) using a matrix and numerical
// transfer function. If we allow more color space representations in the
// framework, we may need to update this maximum size.
-static constexpr uint32_t kMaxColorSpaceSerializedBytes = 80;
+static constexpr size_t kMaxColorSpaceSerializedBytes = 80;
+
+static constexpr auto RuntimeException = "java/lang/RuntimeException";
+
+static bool validateImageInfo(const SkImageInfo& info, int32_t rowBytes) {
+ // TODO: Can we avoid making a SkBitmap for this?
+ return SkBitmap().setInfo(info, rowBytes);
+}
static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) {
#ifdef __ANDROID__ // Layoutlib does not support parcel
if (parcel == NULL) {
- SkDebugf("-------- unparcel parcel is NULL\n");
+ jniThrowNullPointerException(env, "parcel cannot be null");
return NULL;
}
- android::Parcel* p = parcelForJavaObject(env, parcel);
+ ScopedParcel p(env, parcel);
- const SkColorType colorType = (SkColorType)p->readInt32();
- const SkAlphaType alphaType = (SkAlphaType)p->readInt32();
- const uint32_t colorSpaceSize = p->readUint32();
+ const SkColorType colorType = static_cast<SkColorType>(p.readInt32());
+ const SkAlphaType alphaType = static_cast<SkAlphaType>(p.readInt32());
sk_sp<SkColorSpace> colorSpace;
- if (colorSpaceSize > 0) {
- if (colorSpaceSize > kMaxColorSpaceSerializedBytes) {
+ const auto optColorSpaceData = p.readData();
+ if (optColorSpaceData) {
+ const auto& colorSpaceData = *optColorSpaceData;
+ if (colorSpaceData->size() > kMaxColorSpaceSerializedBytes) {
ALOGD("Bitmap_createFromParcel: Serialized SkColorSpace is larger than expected: "
- "%d bytes\n", colorSpaceSize);
+ "%zu bytes (max: %zu)\n",
+ colorSpaceData->size(), kMaxColorSpaceSerializedBytes);
}
- const void* data = p->readInplace(colorSpaceSize);
- if (data) {
- colorSpace = SkColorSpace::Deserialize(data, colorSpaceSize);
- } else {
- ALOGD("Bitmap_createFromParcel: Unable to read serialized SkColorSpace data\n");
- }
+ colorSpace = SkColorSpace::Deserialize(colorSpaceData->data(), colorSpaceData->size());
}
- const int width = p->readInt32();
- const int height = p->readInt32();
- const int rowBytes = p->readInt32();
- const int density = p->readInt32();
+ const int32_t width = p.readInt32();
+ const int32_t height = p.readInt32();
+ const int32_t rowBytes = p.readInt32();
+ const int32_t density = p.readInt32();
if (kN32_SkColorType != colorType &&
kRGBA_F16_SkColorType != colorType &&
kRGB_565_SkColorType != colorType &&
kARGB_4444_SkColorType != colorType &&
kAlpha_8_SkColorType != colorType) {
- SkDebugf("Bitmap_createFromParcel unknown colortype: %d\n", colorType);
+ jniThrowExceptionFmt(env, RuntimeException,
+ "Bitmap_createFromParcel unknown colortype: %d\n", colorType);
return NULL;
}
- std::unique_ptr<SkBitmap> bitmap(new SkBitmap);
- if (!bitmap->setInfo(SkImageInfo::Make(width, height, colorType, alphaType, colorSpace),
- rowBytes)) {
+ auto imageInfo = SkImageInfo::Make(width, height, colorType, alphaType, colorSpace);
+ size_t allocationSize = 0;
+ if (!validateImageInfo(imageInfo, rowBytes)) {
+ jniThrowRuntimeException(env, "Received bad SkImageInfo");
return NULL;
}
-
- // Read the bitmap blob.
- size_t size = bitmap->computeByteSize();
- android::Parcel::ReadableBlob blob;
- android::status_t status = p->readBlob(size, &blob);
- if (status) {
- doThrowRE(env, "Could not read bitmap blob.");
+ if (!Bitmap::computeAllocationSize(rowBytes, height, &allocationSize)) {
+ jniThrowExceptionFmt(env, RuntimeException,
+ "Received bad bitmap size: width=%d, height=%d, rowBytes=%d", width,
+ height, rowBytes);
return NULL;
}
-
- // Map the bitmap in place from the ashmem region if possible otherwise copy.
sk_sp<Bitmap> nativeBitmap;
- if (blob.fd() >= 0 && !blob.isMutable()) {
-#if DEBUG_PARCEL
- ALOGD("Bitmap.createFromParcel: mapped contents of bitmap from %s blob "
- "(fds %s)",
- blob.isMutable() ? "mutable" : "immutable",
- p->allowFds() ? "allowed" : "forbidden");
-#endif
- // Dup the file descriptor so we can keep a reference to it after the Parcel
- // is disposed.
- int dupFd = fcntl(blob.fd(), F_DUPFD_CLOEXEC, 0);
- if (dupFd < 0) {
- ALOGE("Error allocating dup fd. Error:%d", errno);
- blob.release();
- doThrowRE(env, "Could not allocate dup blob fd.");
- return NULL;
- }
-
- // Map the pixels in place and take ownership of the ashmem region. We must also respect the
- // rowBytes value already set on the bitmap instead of attempting to compute our own.
- nativeBitmap = Bitmap::createFrom(bitmap->info(), bitmap->rowBytes(), dupFd,
- const_cast<void*>(blob.data()), size, true);
- if (!nativeBitmap) {
- close(dupFd);
- blob.release();
- doThrowRE(env, "Could not allocate ashmem pixel ref.");
- return NULL;
- }
-
- // Clear the blob handle, don't release it.
- blob.clear();
- } else {
-#if DEBUG_PARCEL
- if (blob.fd() >= 0) {
- ALOGD("Bitmap.createFromParcel: copied contents of mutable bitmap "
- "from immutable blob (fds %s)",
- p->allowFds() ? "allowed" : "forbidden");
- } else {
- ALOGD("Bitmap.createFromParcel: copied contents from %s blob "
- "(fds %s)",
- blob.isMutable() ? "mutable" : "immutable",
- p->allowFds() ? "allowed" : "forbidden");
- }
-#endif
-
- // Copy the pixels into a new buffer.
- nativeBitmap = Bitmap::allocateHeapBitmap(bitmap.get());
- if (!nativeBitmap) {
- blob.release();
- doThrowRE(env, "Could not allocate java pixel ref.");
- return NULL;
- }
- memcpy(bitmap->getPixels(), blob.data(), size);
-
- // Release the blob handle.
- blob.release();
+ binder_status_t error = readBlob(
+ p.get(),
+ // In place callback
+ [&](std::unique_ptr<int8_t[]> buffer, int32_t size) {
+ nativeBitmap = Bitmap::allocateHeapBitmap(allocationSize, imageInfo, rowBytes);
+ if (nativeBitmap) {
+ memcpy(nativeBitmap->pixels(), buffer.get(), size);
+ }
+ },
+ // Ashmem callback
+ [&](android::base::unique_fd fd, int32_t size) {
+ void* addr = mmap(nullptr, size, PROT_READ, MAP_SHARED, fd.get(), 0);
+ if (addr == MAP_FAILED) {
+ return;
+ }
+ nativeBitmap =
+ Bitmap::createFrom(imageInfo, rowBytes, fd.release(), addr, size, true);
+ });
+ if (error != STATUS_OK) {
+ // TODO: Stringify the error, see signalExceptionForError in android_util_Binder.cpp
+ jniThrowExceptionFmt(env, RuntimeException, "Failed to read from Parcel, error=%d", error);
+ return nullptr;
+ }
+ if (!nativeBitmap) {
+ jniThrowRuntimeException(env, "Could not allocate java pixel ref.");
+ return nullptr;
}
- return createBitmap(env, nativeBitmap.release(),
- getPremulBitmapCreateFlags(false), NULL, NULL, density);
+ return createBitmap(env, nativeBitmap.release(), getPremulBitmapCreateFlags(false), nullptr,
+ nullptr, density);
#else
- doThrowRE(env, "Cannot use parcels outside of Android");
+ jniThrowRuntimeException(env, "Cannot use parcels outside of Android");
return NULL;
#endif
}
@@ -722,47 +869,37 @@
return JNI_FALSE;
}
- android::Parcel* p = parcelForJavaObject(env, parcel);
+ ScopedParcel p(env, parcel);
SkBitmap bitmap;
auto bitmapWrapper = reinterpret_cast<BitmapWrapper*>(bitmapHandle);
bitmapWrapper->getSkBitmap(&bitmap);
- p->writeInt32(bitmap.colorType());
- p->writeInt32(bitmap.alphaType());
+ p.writeInt32(bitmap.colorType());
+ p.writeInt32(bitmap.alphaType());
SkColorSpace* colorSpace = bitmap.colorSpace();
if (colorSpace != nullptr) {
- sk_sp<SkData> data = colorSpace->serialize();
- size_t size = data->size();
- p->writeUint32(size);
- if (size > 0) {
- if (size > kMaxColorSpaceSerializedBytes) {
- ALOGD("Bitmap_writeToParcel: Serialized SkColorSpace is larger than expected: "
- "%zu bytes\n", size);
- }
-
- p->write(data->data(), size);
- }
+ p.writeData(colorSpace->serialize());
} else {
- p->writeUint32(0);
+ p.writeData(std::nullopt);
}
- p->writeInt32(bitmap.width());
- p->writeInt32(bitmap.height());
- p->writeInt32(bitmap.rowBytes());
- p->writeInt32(density);
+ p.writeInt32(bitmap.width());
+ p.writeInt32(bitmap.height());
+ p.writeInt32(bitmap.rowBytes());
+ p.writeInt32(density);
// Transfer the underlying ashmem region if we have one and it's immutable.
- android::status_t status;
+ binder_status_t status;
int fd = bitmapWrapper->bitmap().getAshmemFd();
- if (fd >= 0 && p->allowFds()) {
+ if (fd >= 0 && p.allowFds()) {
#if DEBUG_PARCEL
ALOGD("Bitmap.writeToParcel: transferring immutable bitmap's ashmem fd as "
- "immutable blob (fds %s)",
- p->allowFds() ? "allowed" : "forbidden");
+ "immutable blob (fds %s)",
+ p.allowFds() ? "allowed" : "forbidden");
#endif
- status = p->writeDupImmutableBlobFileDescriptor(fd);
- if (status) {
+ status = writeBlobFromFd(p.get(), bitmapWrapper->bitmap().getAllocationByteCount(), fd);
+ if (status != STATUS_OK) {
doThrowRE(env, "Could not write bitmap blob file descriptor.");
return JNI_FALSE;
}
@@ -772,25 +909,15 @@
// Copy the bitmap to a new blob.
#if DEBUG_PARCEL
ALOGD("Bitmap.writeToParcel: copying bitmap into new blob (fds %s)",
- p->allowFds() ? "allowed" : "forbidden");
+ p.allowFds() ? "allowed" : "forbidden");
#endif
size_t size = bitmap.computeByteSize();
- android::Parcel::WritableBlob blob;
- status = p->writeBlob(size, false, &blob);
+ status = writeBlob(p.get(), size, bitmap.getPixels());
if (status) {
doThrowRE(env, "Could not copy bitmap to parcel blob.");
return JNI_FALSE;
}
-
- const void* pSrc = bitmap.getPixels();
- if (pSrc == NULL) {
- memset(blob.data(), 0, size);
- } else {
- memcpy(blob.data(), pSrc, size);
- }
-
- blob.release();
return JNI_TRUE;
#else
doThrowRE(env, "Cannot use parcels outside of Android");
@@ -1156,8 +1283,6 @@
};
-const char* const kParcelPathName = "android/os/Parcel";
-
int register_android_graphics_Bitmap(JNIEnv* env)
{
gBitmap_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Bitmap"));
@@ -1175,9 +1300,6 @@
AHardwareBuffer_toHardwareBuffer = (AHB_to_HB)dlsym(handle_, "AHardwareBuffer_toHardwareBuffer");
LOG_ALWAYS_FATAL_IF(AHardwareBuffer_toHardwareBuffer == nullptr,
" Failed to find required symbol AHardwareBuffer_toHardwareBuffer!");
-
- gParcelOffsets.clazz = MakeGlobalRefOrDie(env, FindClassOrDie(env, kParcelPathName));
- gParcelOffsets.mNativePtr = GetFieldIDOrDie(env, gParcelOffsets.clazz, "mNativePtr", "J");
#endif
return android::RegisterMethodsOrDie(env, "android/graphics/Bitmap", gBitmapMethods,
NELEM(gBitmapMethods));