Handle Pdfium errors

... and factor out common code from PdfEditor and PdfRenderer to
PdfUtils.h

Change-Id: If193579d8fccb55a3c2a7e1fa3c935ce410a17c2
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 60e888d..93ff637 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -138,6 +138,7 @@
     android/graphics/pdf/PdfDocument.cpp \
     android/graphics/pdf/PdfEditor.cpp \
     android/graphics/pdf/PdfRenderer.cpp \
+    android/graphics/pdf/PdfUtils.cpp \
     android_media_AudioRecord.cpp \
     android_media_AudioSystem.cpp \
     android_media_AudioTrack.cpp \
diff --git a/core/jni/android/graphics/pdf/PdfEditor.cpp b/core/jni/android/graphics/pdf/PdfEditor.cpp
index 21f3c46..b5960dd 100644
--- a/core/jni/android/graphics/pdf/PdfEditor.cpp
+++ b/core/jni/android/graphics/pdf/PdfEditor.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include "PdfUtils.h"
+
 #include "jni.h"
 #include "JNIHelp.h"
 
@@ -50,79 +52,16 @@
     jfieldID bottom;
 } gRectClassInfo;
 
-// Also used in PdfRenderer.cpp
-int sUnmatchedPdfiumInitRequestCount = 0;
-
-static void initializeLibraryIfNeeded() {
-    if (sUnmatchedPdfiumInitRequestCount == 0) {
-        FPDF_InitLibrary();
-    }
-    sUnmatchedPdfiumInitRequestCount++;
-}
-
-static void destroyLibraryIfNeeded() {
-    sUnmatchedPdfiumInitRequestCount--;
-    if (sUnmatchedPdfiumInitRequestCount == 0) {
-       FPDF_DestroyLibrary();
-    }
-}
-
-static int getBlock(void* param, unsigned long position, unsigned char* outBuffer,
-        unsigned long size) {
-    const int fd = reinterpret_cast<intptr_t>(param);
-    const int readCount = pread(fd, outBuffer, size, position);
-    if (readCount < 0) {
-        ALOGE("Cannot read from file descriptor. Error:%d", errno);
-        return 0;
-    }
-    return 1;
-}
-
-static jlong nativeOpen(JNIEnv* env, jclass thiz, jint fd, jlong size) {
-    initializeLibraryIfNeeded();
-
-    FPDF_FILEACCESS loader;
-    loader.m_FileLen = size;
-    loader.m_Param = reinterpret_cast<void*>(intptr_t(fd));
-    loader.m_GetBlock = &getBlock;
-
-    FPDF_DOCUMENT document = FPDF_LoadCustomDocument(&loader, NULL);
-
-    if (!document) {
-        const long error = FPDF_GetLastError();
-        switch (error) {
-            case FPDF_ERR_PASSWORD:
-            case FPDF_ERR_SECURITY: {
-                jniThrowExceptionFmt(env, "java/lang/SecurityException",
-                        "cannot create document. Error: %ld", error);
-            } break;
-            default: {
-                jniThrowExceptionFmt(env, "java/io/IOException",
-                        "cannot create document. Error: %ld", error);
-            } break;
-        }
-        destroyLibraryIfNeeded();
-        return -1;
-    }
-
-    return reinterpret_cast<jlong>(document);
-}
-
-static void nativeClose(JNIEnv* env, jclass thiz, jlong documentPtr) {
-    FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
-    FPDF_CloseDocument(document);
-    destroyLibraryIfNeeded();
-}
-
-static jint nativeGetPageCount(JNIEnv* env, jclass thiz, jlong documentPtr) {
-    FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
-    return FPDF_GetPageCount(document);
-}
-
 static jint nativeRemovePage(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex) {
     FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
+
     FPDFPage_Delete(document, pageIndex);
-    return FPDF_GetPageCount(document);
+    HANDLE_PDFIUM_ERROR_STATE_WITH_RET_CODE(env, -1)
+
+    int pageCount = FPDF_GetPageCount(document);
+    HANDLE_PDFIUM_ERROR_STATE_WITH_RET_CODE(env, -1)
+
+    return pageCount;
 }
 
 struct PdfToFdWriter : FPDF_FILEWRITE {
@@ -167,8 +106,8 @@
     if (!success) {
         jniThrowExceptionFmt(env, "java/io/IOException",
                 "cannot write to fd. Error: %d", errno);
-        destroyLibraryIfNeeded();
     }
+    HANDLE_PDFIUM_ERROR_STATE(env)
 }
 
 static void nativeSetTransformAndClip(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex,
@@ -181,6 +120,7 @@
                 "cannot open page");
         return;
     }
+    HANDLE_PDFIUM_ERROR_STATE(env);
 
     double width = 0;
     double height = 0;
@@ -191,7 +131,11 @@
                     "cannot get page size");
         return;
     }
-
+    bool isExceptionPending = forwardPdfiumError(env);
+    if (isExceptionPending) {
+        FPDF_ClosePage(page);
+        return;
+    }
 
     // PDF's coordinate system origin is left-bottom while in graphics it
     // is the top-left. So, translate the PDF coordinates to ours.
@@ -208,6 +152,8 @@
 
     SkScalar transformValues[6];
     if (!matrix.asAffine(transformValues)) {
+        FPDF_ClosePage(page);
+
         jniThrowException(env, "java/lang/IllegalArgumentException",
                 "transform matrix has perspective. Only affine matrices are allowed.");
         return;
@@ -221,8 +167,14 @@
     FS_RECTF clip = {(float) clipLeft, (float) clipTop, (float) clipRight, (float) clipBottom};
 
     FPDFPage_TransFormWithClip(page, &transform, &clip);
+    isExceptionPending = forwardPdfiumError(env);
+    if (isExceptionPending) {
+        FPDF_ClosePage(page);
+        return;
+    }
 
     FPDF_ClosePage(page);
+    HANDLE_PDFIUM_ERROR_STATE(env);
 }
 
 static void nativeGetPageSize(JNIEnv* env, jclass thiz, jlong documentPtr,
@@ -235,6 +187,7 @@
                 "cannot open page");
         return;
     }
+    HANDLE_PDFIUM_ERROR_STATE(env);
 
     double width = 0;
     double height = 0;
@@ -245,17 +198,17 @@
                     "cannot get page size");
         return;
     }
+    bool isExceptionPending = forwardPdfiumError(env);
+    if (isExceptionPending) {
+        FPDF_ClosePage(page);
+        return;
+    }
 
     env->SetIntField(outSize, gPointClassInfo.x, width);
     env->SetIntField(outSize, gPointClassInfo.y, height);
 
     FPDF_ClosePage(page);
-}
-
-static jboolean nativeScaleForPrinting(JNIEnv* env, jclass thiz, jlong documentPtr) {
-    FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
-    FPDF_BOOL success = FPDF_VIEWERREF_GetPrintScaling(document);
-    return success ? JNI_TRUE : JNI_FALSE;
+    HANDLE_PDFIUM_ERROR_STATE(env);
 }
 
 static bool nativeGetPageBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex,
@@ -268,6 +221,7 @@
                 "cannot open page");
         return false;
     }
+    HANDLE_PDFIUM_ERROR_STATE_WITH_RET_CODE(env, false);
 
     float left;
     float top;
@@ -277,8 +231,14 @@
     const FPDF_BOOL success = (pageBox == PAGE_BOX_MEDIA)
         ? FPDFPage_GetMediaBox(page, &left, &top, &right, &bottom)
         : FPDFPage_GetCropBox(page, &left, &top, &right, &bottom);
+    bool isExceptionPending = forwardPdfiumError(env);
+    if (isExceptionPending) {
+        FPDF_ClosePage(page);
+        return false;
+    }
 
     FPDF_ClosePage(page);
+    HANDLE_PDFIUM_ERROR_STATE_WITH_RET_CODE(env, false);
 
     if (!success) {
         return false;
@@ -316,6 +276,7 @@
                 "cannot open page");
         return;
     }
+    HANDLE_PDFIUM_ERROR_STATE(env);
 
     const int left = env->GetIntField(box, gRectClassInfo.left);
     const int top = env->GetIntField(box, gRectClassInfo.top);
@@ -327,8 +288,14 @@
     } else {
         FPDFPage_SetCropBox(page, left, top, right, bottom);
     }
+    bool isExceptionPending = forwardPdfiumError(env);
+    if (isExceptionPending) {
+        FPDF_ClosePage(page);
+        return;
+    }
 
     FPDF_ClosePage(page);
+    HANDLE_PDFIUM_ERROR_STATE(env);
 }
 
 static void nativeSetPageMediaBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex,
diff --git a/core/jni/android/graphics/pdf/PdfRenderer.cpp b/core/jni/android/graphics/pdf/PdfRenderer.cpp
index 3ef2c027..6e7b6b8 100644
--- a/core/jni/android/graphics/pdf/PdfRenderer.cpp
+++ b/core/jni/android/graphics/pdf/PdfRenderer.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include "PdfUtils.h"
+
 #include "jni.h"
 #include "JNIHelp.h"
 #include "GraphicsJNI.h"
@@ -43,86 +45,28 @@
     jfieldID y;
 } gPointClassInfo;
 
-// See PdfEditor.cpp
-extern int sUnmatchedPdfiumInitRequestCount;
-
-static void initializeLibraryIfNeeded() {
-    if (sUnmatchedPdfiumInitRequestCount == 0) {
-        FPDF_InitLibrary();
-    }
-    sUnmatchedPdfiumInitRequestCount++;
-}
-
-static void destroyLibraryIfNeeded() {
-    sUnmatchedPdfiumInitRequestCount--;
-    if (sUnmatchedPdfiumInitRequestCount == 0) {
-       FPDF_DestroyLibrary();
-    }
-}
-
-static int getBlock(void* param, unsigned long position, unsigned char* outBuffer,
-        unsigned long size) {
-    const int fd = reinterpret_cast<intptr_t>(param);
-    const int readCount = pread(fd, outBuffer, size, position);
-    if (readCount < 0) {
-        ALOGE("Cannot read from file descriptor. Error:%d", errno);
-        return 0;
-    }
-    return 1;
-}
-
-static jlong nativeCreate(JNIEnv* env, jclass thiz, jint fd, jlong size) {
-    initializeLibraryIfNeeded();
-
-    FPDF_FILEACCESS loader;
-    loader.m_FileLen = size;
-    loader.m_Param = reinterpret_cast<void*>(intptr_t(fd));
-    loader.m_GetBlock = &getBlock;
-
-    FPDF_DOCUMENT document = FPDF_LoadCustomDocument(&loader, NULL);
-
-    if (!document) {
-        const long error = FPDF_GetLastError();
-        switch (error) {
-            case FPDF_ERR_PASSWORD:
-            case FPDF_ERR_SECURITY: {
-                jniThrowExceptionFmt(env, "java/lang/SecurityException",
-                        "cannot create document. Error: %ld", error);
-            } break;
-            default: {
-                jniThrowExceptionFmt(env, "java/io/IOException",
-                        "cannot create document. Error: %ld", error);
-            } break;
-        }
-        destroyLibraryIfNeeded();
-        return -1;
-    }
-
-    return reinterpret_cast<jlong>(document);
-}
-
 static jlong nativeOpenPageAndGetSize(JNIEnv* env, jclass thiz, jlong documentPtr,
         jint pageIndex, jobject outSize) {
     FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
 
     FPDF_PAGE page = FPDF_LoadPage(document, pageIndex);
-
     if (!page) {
         jniThrowException(env, "java/lang/IllegalStateException",
                 "cannot load page");
         return -1;
     }
+    HANDLE_PDFIUM_ERROR_STATE_WITH_RET_CODE(env, -1)
 
     double width = 0;
     double height = 0;
 
-    const int result = FPDF_GetPageSizeByIndex(document, pageIndex, &width, &height);
-
+    int result = FPDF_GetPageSizeByIndex(document, pageIndex, &width, &height);
     if (!result) {
         jniThrowException(env, "java/lang/IllegalStateException",
                     "cannot get page size");
         return -1;
     }
+    HANDLE_PDFIUM_ERROR_STATE_WITH_RET_CODE(env, -1)
 
     env->SetIntField(outSize, gPointClassInfo.x, width);
     env->SetIntField(outSize, gPointClassInfo.y, height);
@@ -133,22 +77,7 @@
 static void nativeClosePage(JNIEnv* env, jclass thiz, jlong pagePtr) {
     FPDF_PAGE page = reinterpret_cast<FPDF_PAGE>(pagePtr);
     FPDF_ClosePage(page);
-}
-
-static void nativeClose(JNIEnv* env, jclass thiz, jlong documentPtr) {
-    FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
-    FPDF_CloseDocument(document);
-    destroyLibraryIfNeeded();
-}
-
-static jint nativeGetPageCount(JNIEnv* env, jclass thiz, jlong documentPtr) {
-    FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
-    return FPDF_GetPageCount(document);
-}
-
-static jboolean nativeScaleForPrinting(JNIEnv* env, jclass thiz, jlong documentPtr) {
-    FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
-    return FPDF_VIEWERREF_GetPrintScaling(document);
+    HANDLE_PDFIUM_ERROR_STATE(env)
 }
 
 static void DropContext(void* data) {
@@ -284,7 +213,7 @@
 }
 
 static const JNINativeMethod gPdfRenderer_Methods[] = {
-    {"nativeCreate", "(IJ)J", (void*) nativeCreate},
+    {"nativeCreate", "(IJ)J", (void*) nativeOpen},
     {"nativeClose", "(J)V", (void*) nativeClose},
     {"nativeGetPageCount", "(J)I", (void*) nativeGetPageCount},
     {"nativeScaleForPrinting", "(J)Z", (void*) nativeScaleForPrinting},
diff --git a/core/jni/android/graphics/pdf/PdfUtils.cpp b/core/jni/android/graphics/pdf/PdfUtils.cpp
new file mode 100644
index 0000000..905a2aa
--- /dev/null
+++ b/core/jni/android/graphics/pdf/PdfUtils.cpp
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PdfUtils.h"
+
+#include "jni.h"
+#include "JNIHelp.h"
+
+#include "fpdfview.h"
+
+#define LOG_TAG "PdfUtils"
+#include <utils/Log.h>
+
+namespace android {
+
+static int sUnmatchedPdfiumInitRequestCount = 0;
+
+int getBlock(void* param, unsigned long position, unsigned char* outBuffer,
+        unsigned long size) {
+    const int fd = reinterpret_cast<intptr_t>(param);
+    const int readCount = pread(fd, outBuffer, size, position);
+    if (readCount < 0) {
+        ALOGE("Cannot read from file descriptor. Error:%d", errno);
+        return 0;
+    }
+    return 1;
+}
+
+// Check if the last pdfium command failed and if so, forward the error to java via an exception. If
+// this function returns true an exception is pending.
+bool forwardPdfiumError(JNIEnv* env) {
+    long error = FPDF_GetLastError();
+    switch (error) {
+        case FPDF_ERR_SUCCESS:
+            return false;
+        case FPDF_ERR_FILE:
+            jniThrowException(env, "java/io/IOException", "file not found or cannot be opened");
+            break;
+        case FPDF_ERR_FORMAT:
+            jniThrowException(env, "java/io/IOException", "file not in PDF format or corrupted");
+            break;
+        case FPDF_ERR_PASSWORD:
+            jniThrowException(env, "java/lang/SecurityException",
+                    "password required or incorrect password");
+            break;
+        case FPDF_ERR_SECURITY:
+            jniThrowException(env, "java/lang/SecurityException", "unsupported security scheme");
+            break;
+        case FPDF_ERR_PAGE:
+            jniThrowException(env, "java/io/IOException", "page not found or content error");
+            break;
+#ifdef PDF_ENABLE_XFA
+        case FPDF_ERR_XFALOAD:
+            jniThrowException(env, "java/lang/Exception", "load XFA error");
+            break;
+        case FPDF_ERR_XFALAYOUT:
+            jniThrowException(env, "java/lang/Exception", "layout XFA error");
+            break;
+#endif  // PDF_ENABLE_XFA
+        case FPDF_ERR_UNKNOWN:
+        default:
+            jniThrowExceptionFmt(env, "java/lang/Exception", "unknown error %d", error);
+    }
+
+    return true;
+}
+
+static bool initializeLibraryIfNeeded(JNIEnv* env) {
+    if (sUnmatchedPdfiumInitRequestCount == 0) {
+        FPDF_InitLibrary();
+
+        HANDLE_PDFIUM_ERROR_STATE_WITH_RET_CODE(env, false);
+    }
+
+    sUnmatchedPdfiumInitRequestCount++;
+    return true;
+}
+
+static void destroyLibraryIfNeeded(JNIEnv* env, bool handleError) {
+    if (sUnmatchedPdfiumInitRequestCount == 1) {
+       FPDF_DestroyLibrary();
+
+       if (handleError) {
+           HANDLE_PDFIUM_ERROR_STATE(env);
+       }
+    }
+
+    sUnmatchedPdfiumInitRequestCount--;
+}
+
+jlong nativeOpen(JNIEnv* env, jclass thiz, jint fd, jlong size) {
+    bool isInitialized = initializeLibraryIfNeeded(env);
+    if (!isInitialized) {
+        return -1;
+    }
+
+    FPDF_FILEACCESS loader;
+    loader.m_FileLen = size;
+    loader.m_Param = reinterpret_cast<void*>(intptr_t(fd));
+    loader.m_GetBlock = &getBlock;
+
+    FPDF_DOCUMENT document = FPDF_LoadCustomDocument(&loader, NULL);
+    if (!document) {
+        forwardPdfiumError(env);
+        destroyLibraryIfNeeded(env, false);
+        return -1;
+    }
+
+    return reinterpret_cast<jlong>(document);
+}
+
+void nativeClose(JNIEnv* env, jclass thiz, jlong documentPtr) {
+    FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
+    FPDF_CloseDocument(document);
+    HANDLE_PDFIUM_ERROR_STATE(env)
+
+    destroyLibraryIfNeeded(env, true);
+}
+
+jint nativeGetPageCount(JNIEnv* env, jclass thiz, jlong documentPtr) {
+    FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
+
+    int pageCount = FPDF_GetPageCount(document);
+    HANDLE_PDFIUM_ERROR_STATE_WITH_RET_CODE(env, -1);
+
+    return pageCount;
+}
+
+jboolean nativeScaleForPrinting(JNIEnv* env, jclass thiz, jlong documentPtr) {
+    FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
+
+    FPDF_BOOL printScaling = FPDF_VIEWERREF_GetPrintScaling(document);
+    HANDLE_PDFIUM_ERROR_STATE_WITH_RET_CODE(env, false);
+
+    return printScaling ? JNI_TRUE : JNI_FALSE;
+}
+
+};
diff --git a/core/jni/android/graphics/pdf/PdfUtils.h b/core/jni/android/graphics/pdf/PdfUtils.h
new file mode 100644
index 0000000..6e3cebd
--- /dev/null
+++ b/core/jni/android/graphics/pdf/PdfUtils.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef PDF_UTILS_H_
+#define PDF_UTILS_H_
+
+#include "jni.h"
+
+namespace android {
+
+int getBlock(void* param, unsigned long position, unsigned char* outBuffer,
+        unsigned long size);
+
+bool forwardPdfiumError(JNIEnv* env);
+
+#define HANDLE_PDFIUM_ERROR_STATE(env)                         \
+        {                                                      \
+            bool isExceptionPending = forwardPdfiumError(env); \
+            if (isExceptionPending) {                          \
+                return;                                        \
+            }                                                  \
+        }
+
+#define HANDLE_PDFIUM_ERROR_STATE_WITH_RET_CODE(env, retCode)  \
+        {                                                      \
+            bool isExceptionPending = forwardPdfiumError(env); \
+            if (isExceptionPending) {                          \
+                return retCode;                                \
+            }                                                  \
+        }
+
+jlong nativeOpen(JNIEnv* env, jclass thiz, jint fd, jlong size);
+void nativeClose(JNIEnv* env, jclass thiz, jlong documentPtr);
+
+jint nativeGetPageCount(JNIEnv* env, jclass thiz, jlong documentPtr);
+jboolean nativeScaleForPrinting(JNIEnv* env, jclass thiz, jlong documentPtr);
+
+};
+
+#endif /* PDF_UTILS_H_ */