1 /*
2  * Copyright 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17 
18 #define LOG_TAG "BitmapTest"
19 
20 #include <jni.h>
21 #include <android/bitmap.h>
22 #include <android/data_space.h>
23 #include <android/hardware_buffer.h>
24 #include <android/hardware_buffer_jni.h>
25 
26 #include "NativeTestHelpers.h"
27 
28 #include <initializer_list>
29 #include <limits>
30 
31 static jclass gOutputStream_class;
32 static jmethodID gOutputStream_writeMethodID;
33 
validateBitmapInfo(JNIEnv * env,jclass,jobject jbitmap,jint width,jint height,jboolean is565)34 static void validateBitmapInfo(JNIEnv* env, jclass, jobject jbitmap, jint width, jint height,
35         jboolean is565) {
36     AndroidBitmapInfo info;
37     int err = 0;
38     err = AndroidBitmap_getInfo(env, jbitmap, &info);
39     ASSERT_EQ(ANDROID_BITMAP_RESULT_SUCCESS, err);
40     ASSERT_TRUE(width >= 0 && height >= 0);
41     ASSERT_EQ((uint32_t) width, info.width);
42     ASSERT_EQ((uint32_t) height, info.height);
43     int32_t format = is565 ? ANDROID_BITMAP_FORMAT_RGB_565 : ANDROID_BITMAP_FORMAT_RGBA_8888;
44     ASSERT_EQ(format, info.format);
45 }
46 
validateNdkAccessFails(JNIEnv * env,jclass,jobject jbitmap)47 static void validateNdkAccessFails(JNIEnv* env, jclass, jobject jbitmap) {
48     void* pixels = nullptr;
49     int err = AndroidBitmap_lockPixels(env, jbitmap, &pixels);
50     ASSERT_EQ(err, ANDROID_BITMAP_RESULT_JNI_EXCEPTION);
51 
52     int32_t dataSpace = AndroidBitmap_getDataSpace(env, jbitmap);
53     ASSERT_EQ(ADATASPACE_UNKNOWN, dataSpace);
54 
55     AHardwareBuffer* buffer;
56     err = AndroidBitmap_getHardwareBuffer(env, jbitmap, &buffer);
57     ASSERT_EQ(err, ANDROID_BITMAP_RESULT_JNI_EXCEPTION);
58 }
59 
fillRgbaHardwareBuffer(JNIEnv * env,jclass,jobject hwBuffer)60 static void fillRgbaHardwareBuffer(JNIEnv* env, jclass, jobject hwBuffer) {
61     AHardwareBuffer* hardware_buffer = AHardwareBuffer_fromHardwareBuffer(env, hwBuffer);
62     AHardwareBuffer_Desc description;
63     AHardwareBuffer_describe(hardware_buffer, &description);
64     ASSERT_EQ(AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, description.format);
65 
66     uint8_t* rgbaBytes;
67     AHardwareBuffer_lock(hardware_buffer,
68                          AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY,
69                          -1,
70                          nullptr,
71                          reinterpret_cast<void**>(&rgbaBytes));
72     int c = 0;
73     for (int y = 0; y < description.width; ++y) {
74         for (int x = 0; x < description.height; ++x) {
75             rgbaBytes[c++] = static_cast<uint8_t>(x % 255);
76             rgbaBytes[c++] = static_cast<uint8_t>(y % 255);
77             rgbaBytes[c++] = 42;
78             rgbaBytes[c++] = 255;
79         }
80     }
81     AHardwareBuffer_unlock(hardware_buffer, nullptr);
82 }
83 
getFormat(JNIEnv * env,jclass,jobject jbitmap)84 static jint getFormat(JNIEnv* env, jclass, jobject jbitmap) {
85     AndroidBitmapInfo info;
86     info.format = ANDROID_BITMAP_FORMAT_NONE;
87     int err = 0;
88     err = AndroidBitmap_getInfo(env, jbitmap, &info);
89     if (err != ANDROID_BITMAP_RESULT_SUCCESS) {
90         fail(env, "AndroidBitmap_getInfo failed, err=%d", err);
91         return ANDROID_BITMAP_FORMAT_NONE;
92     }
93     return info.format;
94 }
95 
testNullBitmap(JNIEnv * env,jclass,jobject jbitmap)96 static void testNullBitmap(JNIEnv* env, jclass, jobject jbitmap) {
97     ASSERT_NE(nullptr, env);
98     AndroidBitmapInfo info;
99     int err = AndroidBitmap_getInfo(env, nullptr, &info);
100     ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
101 
102     err = AndroidBitmap_getInfo(env, jbitmap, nullptr);
103     ASSERT_EQ(err, ANDROID_BITMAP_RESULT_SUCCESS);
104 
105     err = AndroidBitmap_getInfo(nullptr, jbitmap, &info);
106     ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
107 
108     void* pixels = nullptr;
109     err = AndroidBitmap_lockPixels(env, nullptr, &pixels);
110     ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
111 
112     err = AndroidBitmap_lockPixels(env, jbitmap, nullptr);
113     ASSERT_EQ(err, ANDROID_BITMAP_RESULT_SUCCESS);
114 
115     err = AndroidBitmap_lockPixels(nullptr, jbitmap, &pixels);
116     ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
117 
118     err = AndroidBitmap_unlockPixels(env, nullptr);
119     ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
120 
121     err = AndroidBitmap_unlockPixels(nullptr, jbitmap);
122     ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
123 
124     int32_t dataSpace = AndroidBitmap_getDataSpace(env, nullptr);
125     ASSERT_EQ(dataSpace, ADATASPACE_UNKNOWN);
126 
127     dataSpace = AndroidBitmap_getDataSpace(nullptr, jbitmap);
128     ASSERT_EQ(dataSpace, ADATASPACE_UNKNOWN);
129 
130     err = AndroidBitmap_getHardwareBuffer(env, jbitmap, nullptr);
131     ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
132 
133     AHardwareBuffer* buffer;
134     err = AndroidBitmap_getHardwareBuffer(env, nullptr, &buffer);
135     ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
136 
137     err = AndroidBitmap_getHardwareBuffer(nullptr, jbitmap, &buffer);
138     ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
139 }
140 
testInfo(JNIEnv * env,jclass,jobject jbitmap,jint androidBitmapFormat,jint width,jint height,jboolean hasAlpha,jboolean premultiplied,jboolean hardware)141 static void testInfo(JNIEnv* env, jclass, jobject jbitmap, jint androidBitmapFormat,
142                      jint width, jint height, jboolean hasAlpha, jboolean premultiplied,
143                      jboolean hardware) {
144     AndroidBitmapInfo info;
145     int err = AndroidBitmap_getInfo(env, jbitmap, &info);
146     ASSERT_EQ(err, ANDROID_BITMAP_RESULT_SUCCESS);
147 
148     ASSERT_EQ(androidBitmapFormat, info.format);
149     ASSERT_EQ(width, info.width);
150     ASSERT_EQ(height, info.height);
151 
152     int ndkAlpha = (info.flags << ANDROID_BITMAP_FLAGS_ALPHA_SHIFT)
153             & ANDROID_BITMAP_FLAGS_ALPHA_MASK;
154     if (!hasAlpha) {
155         ASSERT_EQ(ndkAlpha, ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE);
156     } else if (premultiplied) {
157         ASSERT_EQ(ndkAlpha, ANDROID_BITMAP_FLAGS_ALPHA_PREMUL);
158     } else {
159         ASSERT_EQ(ndkAlpha, ANDROID_BITMAP_FLAGS_ALPHA_UNPREMUL);
160     }
161 
162     bool ndkHardware = info.flags & ANDROID_BITMAP_FLAGS_IS_HARDWARE;
163     ASSERT_EQ(ndkHardware, hardware);
164 
165     AHardwareBuffer* buffer;
166     err = AndroidBitmap_getHardwareBuffer(env, jbitmap, &buffer);
167     if (hardware) {
168         ASSERT_EQ(err, ANDROID_BITMAP_RESULT_SUCCESS);
169         ASSERT_NE(buffer, nullptr);
170         AHardwareBuffer_release(buffer);
171     } else {
172         ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
173         ASSERT_EQ(buffer, nullptr);
174     }
175 
176     void* pixels = nullptr;
177     err = AndroidBitmap_lockPixels(env, jbitmap, &pixels);
178     if (hardware) {
179         ASSERT_EQ(err, ANDROID_BITMAP_RESULT_JNI_EXCEPTION);
180     } else {
181         ASSERT_EQ(err, ANDROID_BITMAP_RESULT_SUCCESS);
182         err = AndroidBitmap_unlockPixels(env, jbitmap);
183         ASSERT_EQ(err, ANDROID_BITMAP_RESULT_SUCCESS);
184     }
185 }
186 
getDataSpace(JNIEnv * env,jclass,jobject jbitmap)187 static jint getDataSpace(JNIEnv* env, jclass, jobject jbitmap) {
188     return AndroidBitmap_getDataSpace(env, jbitmap);
189 }
190 
191 /**
192  * Class for writing to a java.io.OutputStream. Passed to
193  * AndroidBitmap_compress. This is modelled after SkJavaOutputStream.
194  */
195 class Context {
196 public:
Context(JNIEnv * env,jobject outputStream,jbyteArray storage)197     Context(JNIEnv* env, jobject outputStream, jbyteArray storage)
198         : mEnv(env)
199         , mOutputStream(outputStream)
200         , mStorage(storage)
201         , mCapacity(mEnv->GetArrayLength(mStorage))
202   {}
203 
compress(const void * data,size_t size)204     bool compress(const void* data, size_t size) {
205         while (size > 0) {
206             jint requested = 0;
207             if (size > static_cast<size_t>(mCapacity)) {
208                 requested = mCapacity;
209             } else {
210                 // This is safe because |requested| is clamped to (jint) mCapacity.
211                 requested = static_cast<jint>(size);
212             }
213 
214             mEnv->SetByteArrayRegion(mStorage, 0, requested,
215                                      reinterpret_cast<const jbyte*>(data));
216             if (mEnv->ExceptionCheck()) {
217                 mEnv->ExceptionDescribe();
218                 mEnv->ExceptionClear();
219                 ALOGE("SetByteArrayRegion threw an exception!");
220                 return false;
221             }
222 
223             mEnv->CallVoidMethod(mOutputStream, gOutputStream_writeMethodID, mStorage, 0,
224                                  requested);
225             if (mEnv->ExceptionCheck()) {
226                 mEnv->ExceptionDescribe();
227                 mEnv->ExceptionClear();
228                 ALOGE("write threw an exception!");
229                 return false;
230             }
231 
232             data = (void*)((char*) data + requested);
233             size -= requested;
234         }
235         return true;
236     }
237 
238 private:
239     JNIEnv* mEnv;
240     jobject mOutputStream;
241     jbyteArray mStorage;
242     jint mCapacity;
243 
244     Context(const Context& other) = delete;
245     Context& operator=(const Context& other) = delete;
246 };
247 
compressWriteFn(void * userContext,const void * data,size_t size)248 static bool compressWriteFn(void* userContext, const void* data, size_t size) {
249     Context* c = reinterpret_cast<Context*>(userContext);
250     return c->compress(data, size);
251 }
252 
253 #define EXPECT_EQ(msg, a, b)    \
254     if ((a) != (b)) {           \
255         ALOGE(msg);             \
256         return false;           \
257     }
258 
compress(JNIEnv * env,jclass,jobject jbitmap,jint format,jint quality,jobject joutputStream,jbyteArray jstorage)259 static jboolean compress(JNIEnv* env, jclass, jobject jbitmap, jint format, jint quality,
260                          jobject joutputStream, jbyteArray jstorage) {
261     AndroidBitmapInfo info;
262     int err = AndroidBitmap_getInfo(env, jbitmap, &info);
263     EXPECT_EQ("Failed to getInfo!", err, ANDROID_BITMAP_RESULT_SUCCESS);
264 
265     void* pixels = nullptr;
266     err = AndroidBitmap_lockPixels(env, jbitmap, &pixels);
267     EXPECT_EQ("Failed to lockPixels!", err, ANDROID_BITMAP_RESULT_SUCCESS);
268 
269     Context context(env, joutputStream, jstorage);
270     err = AndroidBitmap_compress(&info, AndroidBitmap_getDataSpace(env, jbitmap), pixels, format,
271                                  quality, &context, compressWriteFn);
272     if (AndroidBitmap_unlockPixels(env, jbitmap) != ANDROID_BITMAP_RESULT_SUCCESS) {
273         fail(env, "Failed to unlock pixels!");
274     }
275     return err == ANDROID_BITMAP_RESULT_SUCCESS;
276 }
277 
278 constexpr AndroidBitmapCompressFormat gFormats[] = {
279     ANDROID_BITMAP_COMPRESS_FORMAT_JPEG,
280     ANDROID_BITMAP_COMPRESS_FORMAT_PNG,
281     ANDROID_BITMAP_COMPRESS_FORMAT_WEBP_LOSSY,
282     ANDROID_BITMAP_COMPRESS_FORMAT_WEBP_LOSSLESS,
283 };
284 
285 constexpr auto kMaxInt32 = std::numeric_limits<int32_t>::max();
286 
287 /**
288  * Class for generating invalid AndroidBitmapInfos based on a valid one.
289  */
290 class BadInfoGenerator {
291 public:
BadInfoGenerator(const AndroidBitmapInfo & info)292     BadInfoGenerator(const AndroidBitmapInfo& info)
293         : mOriginalInfo(info)
294         , mIter(0)
295     {}
296 
297     /**
298      * Each call to this method will return a pointer to an invalid
299      * AndroidBitmapInfo, until it has run out of infos to generate, when it
300      * will return null.
301      */
next()302     AndroidBitmapInfo* next() {
303         mBadInfo = mOriginalInfo;
304         switch (mIter) {
305             case 0:
306                 mBadInfo.width = -mBadInfo.width;
307                 break;
308             case 1:
309                 mBadInfo.height = -mBadInfo.height;
310                 break;
311             case 2:
312                 mBadInfo.width = - mBadInfo.width;
313                 break;
314             case 3:
315                 // AndroidBitmap_compress uses ANDROID_BITMAP_FLAGS_ALPHA_MASK
316                 // to ignore bits outside of alpha, so the only invalid value is
317                 // 3.
318                 mBadInfo.flags = 3;
319                 break;
320             case 4:
321                 mBadInfo.stride = mBadInfo.stride / 2;
322                 break;
323             case 5:
324                 mBadInfo.format = ANDROID_BITMAP_FORMAT_NONE;
325                 break;
326             case 6:
327                 mBadInfo.format = ANDROID_BITMAP_FORMAT_RGBA_4444;
328                 break;
329             case 7:
330                 mBadInfo.format = -1;
331                 break;
332             case 8:
333                 mBadInfo.format = 2;
334                 break;
335             case 9:
336                 mBadInfo.format = 3;
337                 break;
338             case 10:
339                 mBadInfo.format = 5;
340                 break;
341             case 11:
342                 mBadInfo.format = 6;
343                 break;
344             case 12:
345                 mBadInfo.format = 10;
346                 break;
347             case 13:
348                 mBadInfo.width = static_cast<uint32_t>(kMaxInt32) + 1;
349                 mBadInfo.height = 1;
350                 break;
351             case 14:
352                 mBadInfo.width = 1;
353                 mBadInfo.height = static_cast<uint32_t>(kMaxInt32) + 1;
354                 break;
355             case 15:
356                 mBadInfo.width = 3;
357                 mBadInfo.height = kMaxInt32 / 2;
358                 break;
359             default:
360               return nullptr;
361         }
362         mIter++;
363         return &mBadInfo;
364     }
365 private:
366     const AndroidBitmapInfo& mOriginalInfo;
367     AndroidBitmapInfo mBadInfo;
368     int mIter;
369 
370     BadInfoGenerator(const BadInfoGenerator&) = delete;
371     BadInfoGenerator& operator=(const BadInfoGenerator&) = delete;
372 };
373 
testNdkCompressBadParameter(JNIEnv * env,jclass,jobject jbitmap,jobject joutputStream,jbyteArray jstorage)374 static void testNdkCompressBadParameter(JNIEnv* env, jclass, jobject jbitmap,
375                          jobject joutputStream, jbyteArray jstorage) {
376     AndroidBitmapInfo info;
377     int err = AndroidBitmap_getInfo(env, jbitmap, &info);
378     ASSERT_EQ(err, ANDROID_BITMAP_RESULT_SUCCESS);
379 
380     void* pixels = nullptr;
381     err = AndroidBitmap_lockPixels(env, jbitmap, &pixels);
382     ASSERT_EQ(err, ANDROID_BITMAP_RESULT_SUCCESS);
383 
384     Context context(env, joutputStream, jstorage);
385     int32_t dataSpace = AndroidBitmap_getDataSpace(env, jbitmap);
386 
387     // Bad info.
388     for (auto format : gFormats) {
389         err = AndroidBitmap_compress(nullptr, dataSpace, pixels, format, 100,
390                                      &context, compressWriteFn);
391         ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
392     }
393 
394     {
395         BadInfoGenerator gen(info);
396         while (const auto* badInfo = gen.next()) {
397             for (auto format : gFormats) {
398                 err = AndroidBitmap_compress(badInfo, dataSpace, pixels, format, 100,
399                                              &context, compressWriteFn);
400                 ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
401             }
402         }
403     }
404 
405     for (int32_t badDataSpace : std::initializer_list<int32_t>{ ADATASPACE_UNKNOWN, -1 }) {
406         for (auto format : gFormats) {
407             err = AndroidBitmap_compress(&info, badDataSpace, pixels, format, 100,
408                                          &context, compressWriteFn);
409             ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
410         }
411     }
412 
413     // Bad pixels
414     for (auto format : gFormats) {
415         err = AndroidBitmap_compress(&info, dataSpace, nullptr, format, 100,
416                                      &context, compressWriteFn);
417         ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
418     }
419 
420     // Bad formats
421     for (int32_t badFormat : { -1, 2, 5, 16 }) {
422         err = AndroidBitmap_compress(&info, dataSpace, pixels, badFormat, 100,
423                                      &context, compressWriteFn);
424         ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
425     }
426 
427     // Bad qualities
428     for (int32_t badQuality : { -1, 101, 1024 }) {
429         for (auto format : gFormats) {
430             err = AndroidBitmap_compress(&info, dataSpace, pixels, format, badQuality,
431                                          &context, compressWriteFn);
432             ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
433         }
434     }
435 
436     // Missing compress function
437     for (auto format : gFormats) {
438         err = AndroidBitmap_compress(&info, dataSpace, pixels, format, 100,
439                                      &context, nullptr);
440         ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
441     }
442 
443     // No reason to force client to have a context. (Maybe they'll use a global
444     // variable?) Demonstrate that it's possible to use a null context.
445     auto emptyCompress = [](void*, const void*, size_t) {
446         return true;
447     };
448     for (auto format : gFormats) {
449         err = AndroidBitmap_compress(&info, dataSpace, pixels, format, 100,
450                                      nullptr, emptyCompress);
451         ASSERT_EQ(err, ANDROID_BITMAP_RESULT_SUCCESS);
452     }
453 }
454 
455 static JNINativeMethod gMethods[] = {
456     { "nValidateBitmapInfo", "(Landroid/graphics/Bitmap;IIZ)V",
457         (void*) validateBitmapInfo },
458     { "nValidateNdkAccessFails", "(Landroid/graphics/Bitmap;)V",
459         (void*) validateNdkAccessFails },
460     { "nFillRgbaHwBuffer", "(Landroid/hardware/HardwareBuffer;)V",
461         (void*) fillRgbaHardwareBuffer },
462     { "nGetFormat", "(Landroid/graphics/Bitmap;)I", (void*) getFormat },
463     { "nTestNullBitmap", "(Landroid/graphics/Bitmap;)V", (void*) testNullBitmap },
464     { "nTestInfo", "(Landroid/graphics/Bitmap;IIIZZZ)V", (void*) testInfo },
465     { "nGetDataSpace", "(Landroid/graphics/Bitmap;)I", (void*) getDataSpace },
466     { "nCompress", "(Landroid/graphics/Bitmap;IILjava/io/OutputStream;[B)Z", (void*) compress },
467     { "nTestNdkCompressBadParameter", "(Landroid/graphics/Bitmap;Ljava/io/OutputStream;[B)V",
468         (void*) testNdkCompressBadParameter },
469 };
470 
register_android_graphics_cts_BitmapTest(JNIEnv * env)471 int register_android_graphics_cts_BitmapTest(JNIEnv* env) {
472     gOutputStream_class = reinterpret_cast<jclass>(env->NewGlobalRef(
473             env->FindClass("java/io/OutputStream")));
474     if (!gOutputStream_class) {
475         ALOGE("Could not find (or create a global ref on) OutputStream!");
476         return JNI_ERR;
477     }
478     gOutputStream_writeMethodID = env->GetMethodID(gOutputStream_class, "write", "([BII)V");
479     if (!gOutputStream_writeMethodID) {
480         ALOGE("Could not find method write()!");
481         return JNI_ERR;
482     }
483     jclass clazz = env->FindClass("android/graphics/cts/BitmapTest");
484     return env->RegisterNatives(clazz, gMethods,
485             sizeof(gMethods) / sizeof(JNINativeMethod));
486 }
487