1/*
2 * Copyright (C) 2014 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#define LOG_TAG "BitmapSerializeUtils"
18
19#include <jni.h>
20#include <JNIHelp.h>
21
22#include <android/bitmap.h>
23#include <android/log.h>
24
25namespace android {
26
27#define RGBA_8888_COLOR_DEPTH 4
28
29static bool writeAllBytes(const int fd, void* buffer, const size_t byteCount) {
30    char* writeBuffer = static_cast<char*>(buffer);
31    size_t remainingBytes = byteCount;
32    while (remainingBytes > 0) {
33        ssize_t writtenByteCount = write(fd, writeBuffer, remainingBytes);
34        if (writtenByteCount == -1) {
35            if (errno == EINTR) {
36                continue;
37            }
38            __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,
39                    "Error writing to buffer: %d", errno);
40            return false;
41        }
42        remainingBytes -= writtenByteCount;
43        writeBuffer += writtenByteCount;
44    }
45    return true;
46}
47
48static bool readAllBytes(const int fd, void* buffer, const size_t byteCount) {
49    char* readBuffer = static_cast<char*>(buffer);
50    size_t remainingBytes = byteCount;
51    while (remainingBytes > 0) {
52        ssize_t readByteCount = read(fd, readBuffer, remainingBytes);
53
54        remainingBytes -= readByteCount;
55        readBuffer += readByteCount;
56
57        if (readByteCount == -1) {
58            if (errno == EINTR) {
59                continue;
60            }
61            __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,
62                    "Error reading from buffer: %d", errno);
63            return false;
64        } else if (readByteCount == 0 && remainingBytes > 0) {
65            __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,
66                    "File closed before all bytes were read. %zu/%zu remaining", remainingBytes,
67                    byteCount);
68            return false;
69        }
70    }
71    return true;
72}
73
74static void throwException(JNIEnv* env, const char* className, const char* message) {
75    jclass exceptionClass = env->FindClass(className);
76    env->ThrowNew(exceptionClass, message);
77}
78
79static void throwIllegalStateException(JNIEnv* env, char *message) {
80    const char* className = "java/lang/IllegalStateException";
81    throwException(env, className, message);
82}
83
84static void throwIllegalArgumentException(JNIEnv* env, char* message) {
85    const char* className = "java/lang/IllegalArgumentException";
86    throwException(env, className, message);
87}
88
89static void readBitmapPixels(JNIEnv* env, jclass /* clazz */, jobject jbitmap, jint fd) {
90    // Read the info.
91    AndroidBitmapInfo readInfo;
92    bool read = readAllBytes(fd, (void*) &readInfo, sizeof(AndroidBitmapInfo));
93    if (!read) {
94        throwIllegalStateException(env, (char*) "Cannot read bitmap info");
95        return;
96    }
97
98    // Get the info of the target bitmap.
99    AndroidBitmapInfo targetInfo;
100    int result = AndroidBitmap_getInfo(env, jbitmap, &targetInfo);
101    if (result < 0) {
102        throwIllegalStateException(env, (char*) "Cannot get bitmap info");
103        return;
104    }
105
106    // Enforce we can reuse the bitmap.
107    if (readInfo.width != targetInfo.width || readInfo.height != targetInfo.height
108            || readInfo.stride != targetInfo.stride || readInfo.format != targetInfo.format
109            || readInfo.flags != targetInfo.flags) {
110        throwIllegalArgumentException(env, (char*) "Cannot reuse bitmap");
111        return;
112    }
113
114    // Lock the pixels.
115    void* pixels;
116    result = AndroidBitmap_lockPixels(env, jbitmap, &pixels);
117    if (result < 0) {
118        throwIllegalStateException(env, (char*) "Cannot lock bitmap pixels");
119        return;
120    }
121
122    // Read the pixels.
123    size_t byteCount = readInfo.stride * readInfo.height;
124    read = readAllBytes(fd, (void*) pixels, byteCount);
125    if (!read) {
126        throwIllegalStateException(env, (char*) "Cannot read bitmap pixels");
127        return;
128    }
129
130    // Unlock the pixels.
131    result = AndroidBitmap_unlockPixels(env, jbitmap);
132    if (result < 0) {
133        throwIllegalStateException(env, (char*) "Cannot unlock bitmap pixels");
134    }
135}
136
137static void writeBitmapPixels(JNIEnv* env, jclass /* clazz */, jobject jbitmap, jint fd) {
138    // Get the info.
139    AndroidBitmapInfo info;
140    int result = AndroidBitmap_getInfo(env, jbitmap, &info);
141    if (result < 0) {
142        throwIllegalStateException(env, (char*) "Cannot get bitmap info");
143        return;
144    }
145
146    // Write the info.
147    bool written = writeAllBytes(fd, (void*) &info, sizeof(AndroidBitmapInfo));
148    if (!written) {
149        throwIllegalStateException(env, (char*) "Cannot write bitmap info");
150        return;
151    }
152
153    // Lock the pixels.
154    void* pixels;
155    result = AndroidBitmap_lockPixels(env, jbitmap, &pixels);
156    if (result < 0) {
157        throwIllegalStateException(env, (char*) "Cannot lock bitmap pixels");
158        return;
159    }
160
161    // Write the pixels.
162    size_t byteCount = info.stride * info.height;
163    written = writeAllBytes(fd, (void*) pixels, byteCount);
164    if (!written) {
165        throwIllegalStateException(env, (char*) "Cannot write bitmap pixels");
166        return;
167    }
168
169    // Unlock the pixels.
170    result = AndroidBitmap_unlockPixels(env, jbitmap);
171    if (result < 0) {
172        throwIllegalStateException(env, (char*) "Cannot unlock bitmap pixels");
173    }
174}
175
176static const JNINativeMethod sMethods[] = {
177    {"nativeReadBitmapPixels", "(Landroid/graphics/Bitmap;I)V", (void *) readBitmapPixels},
178    {"nativeWriteBitmapPixels", "(Landroid/graphics/Bitmap;I)V", (void *) writeBitmapPixels},
179};
180
181int register_com_android_printspooler_util_BitmapSerializeUtils(JNIEnv* env) {
182    return jniRegisterNativeMethods(env, "com/android/printspooler/util/BitmapSerializeUtils",
183        sMethods, NELEM(sMethods));
184}
185
186}
187
188jint JNI_OnLoad(JavaVM* jvm, void*) {
189    JNIEnv *env = NULL;
190    if (jvm->GetEnv((void**) &env, JNI_VERSION_1_6)) {
191        return JNI_ERR;
192    }
193
194    if (android::register_com_android_printspooler_util_BitmapSerializeUtils(env) == -1) {
195        return JNI_ERR;
196    }
197
198    return JNI_VERSION_1_6;
199}
200