1 /*
2  * Copyright (C) 2024 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 #include "pdf_document_jni.h"
18 
19 #include <android/bitmap.h>
20 #include <assert.h>
21 #include <jni.h>
22 #include <stdio.h>
23 #include <sys/mman.h>
24 #include <unistd.h>
25 
26 #include <memory>
27 #include <mutex>
28 #include <string>
29 #include <unordered_set>
30 
31 #include "document.h"
32 #include "fcntl.h"
33 #include "file.h"
34 #include "form_widget_info.h"
35 #include "jni_conversion.h"
36 #include "logging.h"
37 #include "page.h"
38 #include "rect.h"
39 // #include "util/java/scoped_local_ref.h"
40 #include <unistd.h>
41 
42 #define LOG_TAG "pdf_document_jni"
43 
44 using pdfClient::Document;
45 using pdfClient::FileReader;
46 using pdfClient::GotoLink;
47 using pdfClient::Page;
48 using pdfClient::Point_i;
49 using pdfClient::Rectangle_i;
50 using pdfClient::SelectionBoundary;
51 using pdfClient::Status;
52 using std::vector;
53 
54 using pdfClient::LinuxFileOps;
55 
56 namespace {
57 std::mutex mutex_;
58 
59 /** Matrix organizes its values in row-major order. These constants correspond to each
60  * value in Matrix.
61  */
62 constexpr int kMScaleX = 0;  // horizontal scale factor
63 constexpr int kMSkewX = 1;   // horizontal skew factor
64 constexpr int kMTransX = 2;  // horizontal translation
65 constexpr int kMSkewY = 3;   // vertical skew factor
66 constexpr int kMScaleY = 4;  // vertical scale factor
67 constexpr int kMTransY = 5;  // vertical translation
68 constexpr int kMPersp0 = 6;  // input x perspective factor
69 constexpr int kMPersp1 = 7;  // input y perspective factor
70 constexpr int kMPersp2 = 8;  // perspective bias
71 }  // namespace
72 
JNI_OnLoad(JavaVM * vm,void * reserved)73 JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
74     std::unique_lock<std::mutex> lock(mutex_);
75     pdfClient::InitLibrary();
76     // NOTE(olsen): We never call FPDF_DestroyLibrary. Would it add any benefit?
77     return JNI_VERSION_1_6;
78 }
79 
Java_android_graphics_pdf_PdfDocumentProxy_createFromFd(JNIEnv * env,jobject obj,jint jfd,jstring jpassword)80 JNIEXPORT jobject JNICALL Java_android_graphics_pdf_PdfDocumentProxy_createFromFd(
81         JNIEnv* env, jobject obj, jint jfd, jstring jpassword) {
82     std::unique_lock<std::mutex> lock(mutex_);
83     LinuxFileOps::FDCloser fd(jfd);
84     const char* password = jpassword == NULL ? NULL : env->GetStringUTFChars(jpassword, NULL);
85     LOGD("Creating FPDF_DOCUMENT from fd: %d", fd.get());
86     std::unique_ptr<Document> doc;
87 
88     auto fileReader = std::make_unique<FileReader>(std::move(fd));
89     size_t pdfSizeInBytes = fileReader->CompleteSize();
90     Status status = Document::Load(std::move(fileReader), password,
91                                    /* closeFdOnFailure= */ true, &doc);
92 
93     if (password) {
94         env->ReleaseStringUTFChars(jpassword, password);
95     }
96     // doc is owned by the LoadPdfResult in java.
97     return convert::ToJavaLoadPdfResult(env, status, std::move(doc), pdfSizeInBytes);
98 }
99 
Java_android_graphics_pdf_PdfDocumentProxy_destroy(JNIEnv * env,jobject jPdfDocument)100 JNIEXPORT void JNICALL Java_android_graphics_pdf_PdfDocumentProxy_destroy(JNIEnv* env,
101                                                                           jobject jPdfDocument) {
102     std::unique_lock<std::mutex> lock(mutex_);
103     Document* doc = convert::GetPdfDocPtr(env, jPdfDocument);
104     LOGD("Deleting Document: %p", doc);
105     delete doc;
106     LOGD("Destroyed Document: %p", doc);
107 }
108 
Java_android_graphics_pdf_PdfDocumentProxy_saveToFd(JNIEnv * env,jobject jPdfDocument,jint jfd)109 JNIEXPORT jboolean JNICALL Java_android_graphics_pdf_PdfDocumentProxy_saveToFd(JNIEnv* env,
110                                                                                jobject jPdfDocument,
111                                                                                jint jfd) {
112     std::unique_lock<std::mutex> lock(mutex_);
113     LinuxFileOps::FDCloser fd(jfd);
114     Document* doc = convert::GetPdfDocPtr(env, jPdfDocument);
115     LOGD("Saving Document %p to fd %d", doc, fd.get());
116     return doc->SaveAs(std::move(fd));
117 }
118 
119 // TODO(b/321979602): Cleanup Dimensions, reusing `android.util.Size`
Java_android_graphics_pdf_PdfDocumentProxy_getPageDimensions(JNIEnv * env,jobject jPdfDocument,jint pageNum)120 JNIEXPORT jobject JNICALL Java_android_graphics_pdf_PdfDocumentProxy_getPageDimensions(
121         JNIEnv* env, jobject jPdfDocument, jint pageNum) {
122     std::unique_lock<std::mutex> lock(mutex_);
123     Document* doc = convert::GetPdfDocPtr(env, jPdfDocument);
124     std::shared_ptr<Page> page = doc->GetPage(pageNum);
125     Rectangle_i dimensions = page->Dimensions();
126     if (pdfClient::IsEmpty(dimensions)) {
127         LOGE("pdfClient returned 0x0 page dimensions for page %d", pageNum);
128         dimensions = pdfClient::IntRect(0, 0, 612, 792);  // Default to Letter size.
129     }
130     return convert::ToJavaDimensions(env, dimensions);
131 }
132 
Java_android_graphics_pdf_PdfDocumentProxy_getPageWidth(JNIEnv * env,jobject jPdfDocument,jint pageNum)133 JNIEXPORT jint JNICALL Java_android_graphics_pdf_PdfDocumentProxy_getPageWidth(JNIEnv* env,
134                                                                                jobject jPdfDocument,
135                                                                                jint pageNum) {
136     std::unique_lock<std::mutex> lock(mutex_);
137     Document* doc = convert::GetPdfDocPtr(env, jPdfDocument);
138     std::shared_ptr<Page> page = doc->GetPage(pageNum);
139     return page->Width();
140 }
141 
Java_android_graphics_pdf_PdfDocumentProxy_getPageHeight(JNIEnv * env,jobject jPdfDocument,jint pageNum)142 JNIEXPORT jint JNICALL Java_android_graphics_pdf_PdfDocumentProxy_getPageHeight(JNIEnv* env,
143                                                                                jobject jPdfDocument,
144                                                                                jint pageNum) {
145     std::unique_lock<std::mutex> lock(mutex_);
146     Document* doc = convert::GetPdfDocPtr(env, jPdfDocument);
147     std::shared_ptr<Page> page = doc->GetPage(pageNum);
148     return page->Height();
149 }
150 
Java_android_graphics_pdf_PdfDocumentProxy_render(JNIEnv * env,jobject jPdfDocument,jint pageNum,jobject jbitmap,jint clipLeft,jint clipTop,jint clipRight,jint clipBottom,jfloatArray jTransform,jint renderMode,jint showAnnotTypes,jboolean renderFormFields)151 JNIEXPORT jboolean JNICALL Java_android_graphics_pdf_PdfDocumentProxy_render(
152         JNIEnv* env, jobject jPdfDocument, jint pageNum, jobject jbitmap, jint clipLeft,
153         jint clipTop, jint clipRight, jint clipBottom, jfloatArray jTransform, jint renderMode,
154         jint showAnnotTypes, jboolean renderFormFields) {
155     std::unique_lock<std::mutex> lock(mutex_);
156     Document* doc = convert::GetPdfDocPtr(env, jPdfDocument);
157 
158     // android.graphics.Bitmap -> FPDF_Bitmap
159     void* bitmap_pixels;
160     if (AndroidBitmap_lockPixels(env, jbitmap, &bitmap_pixels) < 0) {
161         LOGE("Couldn't get bitmap pixel address");
162         return false;
163     }
164     AndroidBitmapInfo info;
165     AndroidBitmap_getInfo(env, jbitmap, &info);
166     const int stride = info.width * 4;
167     FPDF_BITMAP bitmap =
168             FPDFBitmap_CreateEx(info.width, info.height, FPDFBitmap_BGRA, bitmap_pixels, stride);
169 
170     // android.graphics.Matrix (SkMatrix) -> FS_Matrix
171     float transform[9];
172     env->GetFloatArrayRegion(jTransform, 0, 9, transform);
173     if (transform[kMPersp0] != 0 || transform[kMPersp1] != 0 || transform[kMPersp2] != 1) {
174         LOGE("Non-affine transform provided");
175         return false;
176     }
177 
178     FS_MATRIX pdfiumTransform = {transform[kMScaleX], transform[kMSkewY],  transform[kMSkewX],
179                                  transform[kMScaleY], transform[kMTransX], transform[kMTransY]};
180 
181     // Actually render via Page
182     std::shared_ptr<Page> page = doc->GetPage(pageNum);
183     page->Render(bitmap, pdfiumTransform, clipLeft, clipTop, clipRight, clipBottom, renderMode,
184                  showAnnotTypes, renderFormFields);
185     if (AndroidBitmap_unlockPixels(env, jbitmap) < 0) {
186         LOGE("Couldn't unlock bitmap pixel address");
187         return false;
188     }
189     return true;
190 }
191 
Java_android_graphics_pdf_PdfDocumentProxy_cloneWithoutSecurity(JNIEnv * env,jobject jPdfDocument,jint destination)192 JNIEXPORT jboolean JNICALL Java_android_graphics_pdf_PdfDocumentProxy_cloneWithoutSecurity(
193         JNIEnv* env, jobject jPdfDocument, jint destination) {
194     std::unique_lock<std::mutex> lock(mutex_);
195     LinuxFileOps::FDCloser fd(destination);
196     Document* doc = convert::GetPdfDocPtr(env, jPdfDocument);
197     return doc->CloneDocumentWithoutSecurity(std::move(fd));
198 }
199 
Java_android_graphics_pdf_PdfDocumentProxy_getPageText(JNIEnv * env,jobject jPdfDocument,jint pageNum)200 JNIEXPORT jstring JNICALL Java_android_graphics_pdf_PdfDocumentProxy_getPageText(
201         JNIEnv* env, jobject jPdfDocument, jint pageNum) {
202     std::unique_lock<std::mutex> lock(mutex_);
203     Document* doc = convert::GetPdfDocPtr(env, jPdfDocument);
204     std::shared_ptr<Page> page = doc->GetPage(pageNum);
205 
206     std::string text = page->GetTextUtf8();
207     return env->NewStringUTF(text.c_str());
208 }
209 
Java_android_graphics_pdf_PdfDocumentProxy_getPageAltText(JNIEnv * env,jobject jPdfDocument,jint pageNum)210 JNIEXPORT jobject JNICALL Java_android_graphics_pdf_PdfDocumentProxy_getPageAltText(
211         JNIEnv* env, jobject jPdfDocument, jint pageNum) {
212     std::unique_lock<std::mutex> lock(mutex_);
213     Document* doc = convert::GetPdfDocPtr(env, jPdfDocument);
214     std::shared_ptr<Page> page = doc->GetPage(pageNum);
215 
216     vector<std::string> alt_texts;
217     page->GetAltTextUtf8(&alt_texts);
218     return convert::ToJavaStrings(env, alt_texts);
219 }
220 
Java_android_graphics_pdf_PdfDocumentProxy_searchPageText(JNIEnv * env,jobject jPdfDocument,jint pageNum,jstring query)221 JNIEXPORT jobject JNICALL Java_android_graphics_pdf_PdfDocumentProxy_searchPageText(
222         JNIEnv* env, jobject jPdfDocument, jint pageNum, jstring query) {
223     std::unique_lock<std::mutex> lock(mutex_);
224     Document* doc = convert::GetPdfDocPtr(env, jPdfDocument);
225     std::shared_ptr<Page> page = doc->GetPage(pageNum);
226     const char* query_native = env->GetStringUTFChars(query, NULL);
227 
228     vector<Rectangle_i> rects;
229     vector<int> match_to_rect;
230     vector<int> char_indexes;
231     page->BoundsOfMatchesUtf8(query_native, &rects, &match_to_rect, &char_indexes);
232     jobject match_rects = convert::ToJavaMatchRects(env, rects, match_to_rect, char_indexes);
233 
234     env->ReleaseStringUTFChars(query, query_native);
235     return match_rects;
236 }
237 
Java_android_graphics_pdf_PdfDocumentProxy_selectPageText(JNIEnv * env,jobject jPdfDocument,jint pageNum,jobject start,jobject stop)238 JNIEXPORT jobject JNICALL Java_android_graphics_pdf_PdfDocumentProxy_selectPageText(
239         JNIEnv* env, jobject jPdfDocument, jint pageNum, jobject start, jobject stop) {
240     std::unique_lock<std::mutex> lock(mutex_);
241     Document* doc = convert::GetPdfDocPtr(env, jPdfDocument);
242     std::shared_ptr<Page> page = doc->GetPage(pageNum);
243 
244     SelectionBoundary native_start = convert::ToNativeBoundary(env, start);
245     SelectionBoundary native_stop = convert::ToNativeBoundary(env, stop);
246 
247     if (native_start.index == -1 && native_stop.index == -1 &&
248         native_start.point == native_stop.point) {
249         // Starting a new selection at a point.
250         Point_i point = native_start.point;
251         if (!page->SelectWordAt(point, &native_start, &native_stop)) {
252             return NULL;
253         }
254     } else {
255         // Updating an existing selection.
256         page->ConstrainBoundary(&native_start);
257         page->ConstrainBoundary(&native_stop);
258         // Make sure start <= stop - one may have been dragged past the other.
259         if (native_start.index > native_stop.index) {
260             std::swap(native_start, native_stop);
261         }
262     }
263 
264     vector<Rectangle_i> rects;
265     page->GetTextBounds(native_start.index, native_stop.index, &rects);
266     std::string text(page->GetTextUtf8(native_start.index, native_stop.index));
267     return convert::ToJavaSelection(env, pageNum, native_start, native_stop, rects, text);
268 }
269 
Java_android_graphics_pdf_PdfDocumentProxy_getPageLinks(JNIEnv * env,jobject jPdfDocument,jint pageNum)270 JNIEXPORT jobject JNICALL Java_android_graphics_pdf_PdfDocumentProxy_getPageLinks(
271         JNIEnv* env, jobject jPdfDocument, jint pageNum) {
272     std::unique_lock<std::mutex> lock(mutex_);
273     Document* doc = convert::GetPdfDocPtr(env, jPdfDocument);
274     std::shared_ptr<Page> page = doc->GetPage(pageNum);
275 
276     vector<Rectangle_i> rects;
277     vector<int> link_to_rect;
278     vector<std::string> urls;
279     page->GetLinksUtf8(&rects, &link_to_rect, &urls);
280 
281     return convert::ToJavaLinkRects(env, rects, link_to_rect, urls);
282 }
283 
Java_android_graphics_pdf_PdfDocumentProxy_getPageGotoLinks(JNIEnv * env,jobject jPdfDocument,jint pageNum)284 JNIEXPORT jobject JNICALL Java_android_graphics_pdf_PdfDocumentProxy_getPageGotoLinks(
285         JNIEnv* env, jobject jPdfDocument, jint pageNum) {
286     Document* doc = convert::GetPdfDocPtr(env, jPdfDocument);
287     std::shared_ptr<Page> page = doc->GetPage(pageNum);
288 
289     vector<GotoLink> links = page->GetGotoLinks();
290 
291     return convert::ToJavaGotoLinks(env, links);
292 }
293 
Java_android_graphics_pdf_PdfDocumentProxy_retainPage(JNIEnv * env,jobject jPdfDocument,jint pageNum)294 JNIEXPORT void JNICALL Java_android_graphics_pdf_PdfDocumentProxy_retainPage(JNIEnv* env,
295                                                                              jobject jPdfDocument,
296                                                                              jint pageNum) {
297     Document* doc = convert::GetPdfDocPtr(env, jPdfDocument);
298     doc->GetPage(pageNum, true);
299 }
300 
Java_android_graphics_pdf_PdfDocumentProxy_releasePage(JNIEnv * env,jobject jPdfDocument,jint pageNum)301 JNIEXPORT void JNICALL Java_android_graphics_pdf_PdfDocumentProxy_releasePage(JNIEnv* env,
302                                                                               jobject jPdfDocument,
303                                                                               jint pageNum) {
304     Document* doc = convert::GetPdfDocPtr(env, jPdfDocument);
305     doc->ReleaseRetainedPage(pageNum);
306 }
307 
308 JNIEXPORT jboolean JNICALL
Java_android_graphics_pdf_PdfDocumentProxy_scaleForPrinting(JNIEnv * env,jobject jPdfDocument)309 Java_android_graphics_pdf_PdfDocumentProxy_scaleForPrinting(JNIEnv* env, jobject jPdfDocument) {
310     std::unique_lock<std::mutex> lock(mutex_);
311     Document* doc = convert::GetPdfDocPtr(env, jPdfDocument);
312     return doc->ShouldScaleForPrinting();
313 }
314 
315 JNIEXPORT jboolean JNICALL
Java_android_graphics_pdf_PdfDocumentProxy_isPdfLinearized(JNIEnv * env,jobject jPdfDocument)316 Java_android_graphics_pdf_PdfDocumentProxy_isPdfLinearized(JNIEnv* env, jobject jPdfDocument) {
317     std::unique_lock<std::mutex> lock(mutex_);
318     Document* doc = convert::GetPdfDocPtr(env, jPdfDocument);
319     return doc->IsLinearized();
320 }
321 
Java_android_graphics_pdf_PdfDocumentProxy_getFormType(JNIEnv * env,jobject jPdfDocument)322 JNIEXPORT jint JNICALL Java_android_graphics_pdf_PdfDocumentProxy_getFormType(JNIEnv* env,
323                                                                             jobject jPdfDocument) {
324     std::unique_lock<std::mutex> lock(mutex_);
325     Document* doc = convert::GetPdfDocPtr(env, jPdfDocument);
326     return doc->GetFormType();
327 }
328 
Java_android_graphics_pdf_PdfDocumentProxy_getFormWidgetInfo__III(JNIEnv * env,jobject jPdfDocument,jint pageNum,jint x,jint y)329 JNIEXPORT jobject JNICALL Java_android_graphics_pdf_PdfDocumentProxy_getFormWidgetInfo__III(
330         JNIEnv* env, jobject jPdfDocument, jint pageNum, jint x, jint y) {
331     std::unique_lock<std::mutex> lock(mutex_);
332     Document* doc = convert::GetPdfDocPtr(env, jPdfDocument);
333     std::shared_ptr<Page> page = doc->GetPage(pageNum, true);
334 
335     Point_i point{x, y};
336     FormWidgetInfo result = page->GetFormWidgetInfo(point);
337     if (!result.FoundWidget()) {
338         LOGE("No widget found at point x = %d, y = %d", x, y);
339         doc->ReleaseRetainedPage(pageNum);
340         return NULL;
341     }
342 
343     doc->ReleaseRetainedPage(pageNum);
344     return convert::ToJavaFormWidgetInfo(env, result);
345 }
346 
Java_android_graphics_pdf_PdfDocumentProxy_getFormWidgetInfo__II(JNIEnv * env,jobject jPdfDocument,jint pageNum,jint index)347 JNIEXPORT jobject JNICALL Java_android_graphics_pdf_PdfDocumentProxy_getFormWidgetInfo__II(
348         JNIEnv* env, jobject jPdfDocument, jint pageNum, jint index) {
349     std::unique_lock<std::mutex> lock(mutex_);
350     Document* doc = convert::GetPdfDocPtr(env, jPdfDocument);
351     std::shared_ptr<Page> page = doc->GetPage(pageNum, true);
352 
353     FormWidgetInfo result = page->GetFormWidgetInfo(index);
354     if (!result.FoundWidget()) {
355         LOGE("No widget found at this index %d", index);
356         doc->ReleaseRetainedPage(pageNum);
357         return NULL;
358     }
359 
360     doc->ReleaseRetainedPage(pageNum);
361     return convert::ToJavaFormWidgetInfo(env, result);
362 }
363 
Java_android_graphics_pdf_PdfDocumentProxy_getFormWidgetInfos(JNIEnv * env,jobject jPdfDocument,jint pageNum,jintArray jTypeIds)364 JNIEXPORT jobject JNICALL Java_android_graphics_pdf_PdfDocumentProxy_getFormWidgetInfos(
365         JNIEnv* env, jobject jPdfDocument, jint pageNum, jintArray jTypeIds) {
366     std::unique_lock<std::mutex> lock(mutex_);
367     Document* doc = convert::GetPdfDocPtr(env, jPdfDocument);
368     std::shared_ptr<Page> page = doc->GetPage(pageNum, true);
369 
370     std::unordered_set<int> type_ids = convert::ToNativeIntegerUnorderedSet(env, jTypeIds);
371 
372     std::vector<FormWidgetInfo> widget_infos;
373     page->GetFormWidgetInfos(type_ids, &widget_infos);
374 
375     doc->ReleaseRetainedPage(pageNum);
376     return convert::ToJavaFormWidgetInfos(env, widget_infos);
377 }
378 
Java_android_graphics_pdf_PdfDocumentProxy_clickOnPage(JNIEnv * env,jobject jPdfDocument,jint pageNum,jint x,jint y)379 JNIEXPORT jobject JNICALL Java_android_graphics_pdf_PdfDocumentProxy_clickOnPage(
380         JNIEnv* env, jobject jPdfDocument, jint pageNum, jint x, jint y) {
381     std::unique_lock<std::mutex> lock(mutex_);
382     Document* doc = convert::GetPdfDocPtr(env, jPdfDocument);
383     std::shared_ptr<Page> page = doc->GetPage(pageNum, true);
384 
385     Point_i point{x, y};
386     bool clicked = page->ClickOnPoint(point);
387     if (!clicked) {
388         LOGE("Cannot click on this widget");
389         doc->ReleaseRetainedPage(pageNum);
390         return NULL;
391     }
392 
393     vector<Rectangle_i> invalid_rects;
394     if (page->HasInvalidRect()) {
395         invalid_rects.push_back(page->ConsumeInvalidRect());
396     }
397     doc->ReleaseRetainedPage(pageNum);
398     return convert::ToJavaRects(env, invalid_rects);
399 }
400 
Java_android_graphics_pdf_PdfDocumentProxy_setFormFieldText(JNIEnv * env,jobject jPdfDocument,jint pageNum,jint annotationIndex,jstring jText)401 JNIEXPORT jobject JNICALL Java_android_graphics_pdf_PdfDocumentProxy_setFormFieldText(
402         JNIEnv* env, jobject jPdfDocument, jint pageNum, jint annotationIndex, jstring jText) {
403     std::unique_lock<std::mutex> lock(mutex_);
404     Document* doc = convert::GetPdfDocPtr(env, jPdfDocument);
405     std::shared_ptr<Page> page = doc->GetPage(pageNum, true);
406 
407     const char* text = jText == nullptr ? "" : env->GetStringUTFChars(jText, nullptr);
408     bool set = page->SetFormFieldText(annotationIndex, text);
409     if (!set) {
410         LOGE("Cannot set form field text on this widget.");
411         doc->ReleaseRetainedPage(pageNum);
412         return NULL;
413     }
414 
415     if (jText) {
416         env->ReleaseStringUTFChars(jText, text);
417     }
418 
419     vector<Rectangle_i> invalid_rects;
420     if (page->HasInvalidRect()) {
421         invalid_rects.push_back(page->ConsumeInvalidRect());
422     }
423     doc->ReleaseRetainedPage(pageNum);
424     return convert::ToJavaRects(env, invalid_rects);
425 }
426 
Java_android_graphics_pdf_PdfDocumentProxy_setFormFieldSelectedIndices(JNIEnv * env,jobject jPdfDocument,jint pageNum,jint annotationIndex,jintArray jSelectedIndices)427 JNIEXPORT jobject JNICALL Java_android_graphics_pdf_PdfDocumentProxy_setFormFieldSelectedIndices(
428         JNIEnv* env, jobject jPdfDocument, jint pageNum, jint annotationIndex,
429         jintArray jSelectedIndices) {
430     std::unique_lock<std::mutex> lock(mutex_);
431     Document* doc = convert::GetPdfDocPtr(env, jPdfDocument);
432     std::shared_ptr<Page> page = doc->GetPage(pageNum, true);
433 
434     vector<int> selected_indices = convert::ToNativeIntegerVector(env, jSelectedIndices);
435     bool set = page->SetChoiceSelection(annotationIndex, selected_indices);
436     if (!set) {
437         LOGE("Cannot set selected indices on this widget.");
438         doc->ReleaseRetainedPage(pageNum);
439         return NULL;
440     }
441 
442     vector<Rectangle_i> invalid_rects;
443     if (page->HasInvalidRect()) {
444         invalid_rects.push_back(page->ConsumeInvalidRect());
445     }
446     doc->ReleaseRetainedPage(pageNum);
447     return convert::ToJavaRects(env, invalid_rects);
448 }
449