• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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 package android.graphics.pdf;
18 
19 import static android.graphics.pdf.PdfLinearizationTypes.PDF_DOCUMENT_TYPE_LINEARIZED;
20 
21 import android.annotation.FlaggedApi;
22 import android.annotation.IntDef;
23 import android.annotation.IntRange;
24 import android.annotation.Nullable;
25 import android.annotation.SuppressLint;
26 import android.graphics.Bitmap;
27 import android.graphics.Matrix;
28 import android.graphics.Rect;
29 import android.graphics.pdf.content.PdfPageGotoLinkContent;
30 import android.graphics.pdf.content.PdfPageImageContent;
31 import android.graphics.pdf.content.PdfPageLinkContent;
32 import android.graphics.pdf.content.PdfPageTextContent;
33 import android.graphics.pdf.flags.Flags;
34 import android.graphics.pdf.models.FormEditRecord;
35 import android.graphics.pdf.models.FormWidgetInfo;
36 import android.graphics.pdf.models.PageMatchBounds;
37 import android.graphics.pdf.models.selection.PageSelection;
38 import android.graphics.pdf.models.selection.SelectionBoundary;
39 import android.graphics.pdf.utils.Preconditions;
40 import android.os.ParcelFileDescriptor;
41 
42 import androidx.annotation.NonNull;
43 import androidx.annotation.RestrictTo;
44 
45 import java.io.IOException;
46 import java.lang.annotation.Retention;
47 import java.lang.annotation.RetentionPolicy;
48 import java.util.List;
49 
50 /**
51  * <p>
52  * This class enables rendering a PDF document and selecting, searching, fast scrolling,
53  * annotations, etc from Android R till Android U. This class is thread safe and can be called by
54  * multiple threads however only one thread will be executed at a time as the access is
55  * synchronized by internal locking.
56  * <p>
57  * If you want to render a PDF, you will need to create a new instance of renderer for each
58  * document. To render each page, you open the page using the renderer instance created earlier,
59  * render it, and close the page. After you are done with rendering, you close the renderer. After
60  * the renderer is closed it should not be used anymore. Note that the pages are rendered one by
61  * one, i.e. you can have only a single page opened at any given time.
62  * <p>
63  * A typical use of the APIs to render a PDF looks like this:
64  * <pre>
65  * // create a new renderer
66  * try (PdfRendererPreV renderer = new PdfRendererPreV(getSeekableFileDescriptor(), loadParams)) {
67  *      // let us just render all pages
68  *      final int pageCount = renderer.getPageCount();
69  *      for (int i = 0; i < pageCount; i++) {
70  *          Page page = renderer.openPage(i);
71  *          RenderParams params = new RenderParams.Builder(Page.RENDER_MODE_FOR_DISPLAY).build();
72  *
73  *          // say we render for showing on the screen
74  *          page.render(mBitmap, params, null, null);
75  *
76  *          // do stuff with the bitmap
77  *
78  *          // close the page
79  *          page.close();
80  *      }
81  * }
82  * </pre>
83  * <h3>Print preview and print output</h3>
84  * <p>
85  * Please refer to {@link PdfRenderer} for fulfilling this usecase.
86  */
87 @FlaggedApi(Flags.FLAG_ENABLE_PDF_VIEWER)
88 public final class PdfRendererPreV implements AutoCloseable {
89     /** Represents a non-linearized PDF document. */
90     public static final int DOCUMENT_LINEARIZED_TYPE_NON_LINEARIZED = 0;
91     /** Represents a linearized PDF document. */
92     public static final int DOCUMENT_LINEARIZED_TYPE_LINEARIZED = 1;
93 
94     /** Represents a PDF without form fields */
95     @FlaggedApi(Flags.FLAG_ENABLE_FORM_FILLING)
96     public static final int PDF_FORM_TYPE_NONE = 0;
97 
98     /** Represents a PDF with form fields specified using the AcroForm spec */
99     @FlaggedApi(Flags.FLAG_ENABLE_FORM_FILLING)
100     public static final int PDF_FORM_TYPE_ACRO_FORM = 1;
101 
102     /** Represents a PDF with form fields specified using the entire XFA spec */
103     @FlaggedApi(Flags.FLAG_ENABLE_FORM_FILLING)
104     public static final int PDF_FORM_TYPE_XFA_FULL = 2;
105 
106     /** Represents a PDF with form fields specified using the XFAF subset of the XFA spec */
107     @FlaggedApi(Flags.FLAG_ENABLE_FORM_FILLING)
108     public static final int PDF_FORM_TYPE_XFA_FOREGROUND = 3;
109     private final int mPageCount;
110     private PdfProcessor mPdfProcessor;
111 
112     /**
113      * Creates a new instance of PdfRendererPreV class.
114      * <p>
115      * <strong>Note:</strong> The provided file descriptor must be <strong>seekable</strong>,
116      * i.e. its data being randomly accessed, e.g. pointing to a file.
117      * <p>
118      * <strong>Note:</strong> This class takes ownership of the passed in file descriptor
119      * and is responsible for closing it when the renderer is closed.
120      * <p>
121      * If the file is from an untrusted source it is recommended to run the renderer in a separate,
122      * isolated process with minimal permissions to limit the impact of security exploits.
123      *
124      * @param fileDescriptor Seekable file descriptor to read from.
125      * @throws java.io.IOException         If an error occurs while reading the file.
126      * @throws java.lang.SecurityException If the file requires a password or
127      *                                     the security scheme is not supported by the renderer.
128      * @throws IllegalArgumentException    If the {@link ParcelFileDescriptor} is not seekable.
129      * @throws NullPointerException        If the file descriptor is null.
130      */
PdfRendererPreV(@onNull ParcelFileDescriptor fileDescriptor)131     public PdfRendererPreV(@NonNull ParcelFileDescriptor fileDescriptor) throws IOException {
132         Preconditions.checkNotNull(fileDescriptor, "Input FD cannot be null");
133 
134         try {
135             mPdfProcessor = new PdfProcessor();
136             mPdfProcessor.create(fileDescriptor, null);
137             mPageCount = mPdfProcessor.getNumPages();
138         } catch (Throwable t) {
139             doClose();
140             throw t;
141         }
142     }
143 
144     /**
145      * Creates a new instance of PdfRendererPreV class.
146      * <p>
147      * <strong>Note:</strong> The provided file descriptor must be <strong>seekable</strong>,
148      * i.e. its data being randomly accessed, e.g. pointing to a file. If the password passed in
149      * {@link android.graphics.pdf.LoadParams} is incorrect, the
150      * {@link android.graphics.pdf.PdfRendererPreV} will throw a {@link SecurityException}.
151      * <p>
152      * <strong>Note:</strong> This class takes ownership of the passed in file descriptor
153      * and is responsible for closing it when the renderer is closed.
154      * <p>
155      * If the file is from an untrusted source it is recommended to run the renderer in a separate,
156      * isolated process with minimal permissions to limit the impact of security exploits.
157      *
158      * @param fileDescriptor Seekable file descriptor to read from.
159      * @param params         Instance of {@link LoadParams} specifying params for loading PDF
160      *                       document.
161      * @throws java.io.IOException         If an error occurs while reading the file.
162      * @throws java.lang.SecurityException If the file requires a password or
163      *                                     the security scheme is not supported by the renderer.
164      * @throws IllegalArgumentException    If the {@link ParcelFileDescriptor} is not seekable.
165      * @throws NullPointerException        If the file descriptor or load params is null.
166      */
PdfRendererPreV(@onNull ParcelFileDescriptor fileDescriptor, @NonNull LoadParams params)167     public PdfRendererPreV(@NonNull ParcelFileDescriptor fileDescriptor, @NonNull LoadParams params)
168             throws IOException {
169         Preconditions.checkNotNull(fileDescriptor, "Input FD cannot be null");
170         Preconditions.checkNotNull(params, "LoadParams cannot be null");
171 
172         try {
173             mPdfProcessor = new PdfProcessor();
174             mPdfProcessor.create(fileDescriptor, params);
175             mPageCount = mPdfProcessor.getNumPages();
176         } catch (Throwable t) {
177             doClose();
178             throw t;
179         }
180     }
181 
182     /**
183      * Gets the number of pages in the document.
184      *
185      * @return The page count.
186      * @throws IllegalStateException If {@link #close()} is called before invoking this.
187      */
188     @IntRange(from = 0)
getPageCount()189     public int getPageCount() {
190         throwIfDocumentClosed();
191         return mPageCount;
192     }
193 
194     /**
195      * Gets the type of the PDF document.
196      *
197      * @return The PDF document type.
198      * @throws IllegalStateException If {@link #close()} is called before invoking this.
199      */
200     @PdfDocumentLinearizationType
getDocumentLinearizationType()201     public int getDocumentLinearizationType() {
202         throwIfDocumentClosed();
203         int documentType = mPdfProcessor.getDocumentLinearizationType();
204         if (documentType == PDF_DOCUMENT_TYPE_LINEARIZED) {
205             return DOCUMENT_LINEARIZED_TYPE_LINEARIZED;
206         } else {
207             return DOCUMENT_LINEARIZED_TYPE_NON_LINEARIZED;
208         }
209     }
210 
211     /**
212      * Opens a {@link Page} for rendering.
213      *
214      * @param index The page index to open, starting from index 0.
215      * @return A page that can be rendered.
216      * @throws IllegalStateException    If {@link #close()} is called before invoking this.
217      * @throws IllegalArgumentException If the page number is less than 0 or greater than or equal
218      *                                  to the total page count.
219      */
220     @NonNull
openPage(@ntRangefrom = 0) int index)221     public Page openPage(@IntRange(from = 0) int index) {
222         throwIfDocumentClosed();
223         throwIfPageNotInDocument(index);
224         return new Page(index);
225     }
226 
227     /**
228      * Returns the form type of the loaded PDF
229      *
230      * @throws IllegalStateException    if the renderer is closed
231      * @throws IllegalArgumentException if an unexpected PDF form type is returned
232      */
233     @PdfFormType
234     @FlaggedApi(Flags.FLAG_ENABLE_FORM_FILLING)
getPdfFormType()235     public int getPdfFormType() {
236         throwIfDocumentClosed();
237         int pdfFormType = mPdfProcessor.getPdfFormType();
238         if (pdfFormType == PDF_FORM_TYPE_ACRO_FORM) {
239             return PDF_FORM_TYPE_ACRO_FORM;
240         } else if (pdfFormType == PDF_FORM_TYPE_XFA_FULL) {
241             return PDF_FORM_TYPE_XFA_FULL;
242         } else if (pdfFormType == PDF_FORM_TYPE_XFA_FOREGROUND) {
243             return PDF_FORM_TYPE_XFA_FOREGROUND;
244         } else {
245             return PDF_FORM_TYPE_NONE;
246         }
247     }
248 
249     /**
250      * <p>
251      * Saves the current state of the loaded PDF document to the given writable
252      * {@link ParcelFileDescriptor}. If the document is password-protected then setting
253      * {@code removePasswordProtection} removes the protection before saving. The PDF document
254      * should already be decrypted with the correct password before writing. Useful for printing or
255      * sharing.
256      * <strong>Note:</strong> This method closes the provided file descriptor.
257      *
258      * @param destination              The writable {@link ParcelFileDescriptor}
259      * @param removePasswordProtection If true, removes password protection from the PDF before
260      *                                 saving.
261      * @throws IOException           If there's a write error, or if 'removePasswordSecurity' is
262      *                               {@code true} but the document remains encrypted.
263      * @throws IllegalStateException If {@link #close()} is called before invoking this.
264      */
write(@onNull ParcelFileDescriptor destination, boolean removePasswordProtection)265     public void write(@NonNull ParcelFileDescriptor destination, boolean removePasswordProtection)
266             throws IOException {
267         throwIfDocumentClosed();
268         mPdfProcessor.write(destination, removePasswordProtection);
269     }
270 
271     /**
272      * Closes this renderer and destroys any cached instance of the document. You should not use
273      * this instance after this method is called.
274      *
275      * @throws IllegalStateException If {@link #close()} is called before invoking this.
276      */
277     @Override
close()278     public void close() {
279         throwIfDocumentClosed();
280         doClose();
281     }
282 
283     // SuppressLint: Finalize needs to be overridden to make sure all resources are closed
284     // gracefully
285     @Override
286     @SuppressLint("GenericException")
finalize()287     protected void finalize() throws Throwable {
288         try {
289             doClose();
290         } finally {
291             super.finalize();
292         }
293     }
294 
doClose()295     private void doClose() {
296         if (mPdfProcessor != null) {
297             mPdfProcessor.ensurePdfDestroyed();
298             mPdfProcessor = null;
299         }
300     }
301 
throwIfDocumentClosed()302     private void throwIfDocumentClosed() {
303         if (mPdfProcessor == null) {
304             throw new IllegalStateException("Document already closed!");
305         }
306     }
307 
throwIfPageNotInDocument(int pageIndex)308     private void throwIfPageNotInDocument(int pageIndex) {
309         if (pageIndex < 0 || pageIndex >= mPageCount) {
310             throw new IllegalArgumentException("Invalid page index");
311         }
312     }
313 
314     /** @hide */
315     @IntDef({PDF_FORM_TYPE_NONE, PDF_FORM_TYPE_ACRO_FORM, PDF_FORM_TYPE_XFA_FULL,
316             PDF_FORM_TYPE_XFA_FOREGROUND})
317     @Retention(RetentionPolicy.SOURCE)
318     public @interface PdfFormType {
319     }
320 
321     /** @hide */
322     @RestrictTo(RestrictTo.Scope.LIBRARY)
323     @Retention(RetentionPolicy.SOURCE)
324     @IntDef(prefix = {"PDF_DOCUMENT_TYPE_"}, value = {DOCUMENT_LINEARIZED_TYPE_NON_LINEARIZED,
325             DOCUMENT_LINEARIZED_TYPE_LINEARIZED})
326     public @interface PdfDocumentLinearizationType {
327     }
328 
329     /**
330      * This class represents a PDF document page for rendering.
331      */
332     public final class Page implements AutoCloseable {
333         private final int mWidth;
334         private final int mHeight;
335         private int mIndex;
336 
Page(int index)337         private Page(int index) {
338             this.mIndex = index;
339             mWidth = mPdfProcessor.getPageWidth(index);
340             mHeight = mPdfProcessor.getPageHeight(index);
341         }
342 
343         /**
344          * Gets the page index.
345          *
346          * @return The index.
347          */
348         @IntRange(from = 0)
getIndex()349         public int getIndex() {
350             return mIndex;
351         }
352 
353         /**
354          * Renders a page to a bitmap. In case of default zoom, the {@link Bitmap} dimensions will
355          * be equal to the page dimensions. In this case, {@link Rect} parameter can be null.
356          *
357          * <p>In case of zoom, the {@link Rect} parameter needs to be specified which represents
358          * the offset from top and left for tile generation purposes. In this case, the
359          * {@link Bitmap} dimensions should be equal to the tile dimensions.
360          * <p>
361          * <strong>Note:</strong> The method will take care of closing the bitmap. Should be
362          * invoked
363          * on the {@link android.annotation.WorkerThread} as it is long-running task.
364          *
365          * @param destination Destination bitmap to write to.
366          * @param destClip    If null, default zoom is applied. In case the value is non-null, the
367          *                    value specifies the top top-left corner of the tile.
368          * @param transform   Applied to scale the bitmap up/down from default 1/72 points.
369          * @param params      Render params for the changing display mode and/or annotations.
370          * @throws IllegalStateException If the document/page is closed before invocation.
371          */
render(@onNull Bitmap destination, @Nullable Rect destClip, @Nullable Matrix transform, @NonNull RenderParams params)372         public void render(@NonNull Bitmap destination, @Nullable Rect destClip,
373                 @Nullable Matrix transform, @NonNull RenderParams params) {
374             throwIfDocumentOrPageClosed();
375             mPdfProcessor.renderPage(mIndex, destination, destClip, transform,
376                     params, /* renderFormFields= */ true);
377         }
378 
379         /**
380          * Return list of {@link PdfPageTextContent} found on the page, ordered left to right
381          * and top to bottom. It contains all the content associated with text found on the page.
382          * The list will be empty if there are no results found. Currently, localisation does
383          * not have any impact on the order in which {@link PdfPageTextContent} is returned.
384          *
385          * @return list of text content found on the page.
386          * @throws IllegalStateException If the document/page is closed before invocation.
387          */
388         @NonNull
getTextContents()389         public List<PdfPageTextContent> getTextContents() {
390             throwIfDocumentOrPageClosed();
391             return mPdfProcessor.getPageTextContents(mIndex);
392         }
393 
394         /**
395          * Return list of {@link PdfPageImageContent} found on the page, ordered left to right
396          * and top to bottom. It contains all the content associated with images found on the
397          * page including alt text. The list will be empty if there are no results found.
398          * Currently, localisation does not have any impact on the order in which
399          * {@link PdfPageImageContent} is returned.
400          *
401          * @return list of image content found on the page.
402          * @throws IllegalStateException If the document/page is closed before invocation.
403          */
404         @NonNull
getImageContents()405         public List<PdfPageImageContent> getImageContents() {
406             throwIfDocumentOrPageClosed();
407             return mPdfProcessor.getPageImageContents(mIndex);
408         }
409 
410         /**
411          * Returns the width of the given {@link Page} object in points (1/72"). It is not
412          * guaranteed that all pages will have the same width and the viewport should be resized to
413          * the given page width.
414          *
415          * @return width of the given page
416          * @throws IllegalStateException If the document/page is closed before invocation.
417          */
418         @IntRange(from = 0)
getWidth()419         public int getWidth() {
420             throwIfDocumentOrPageClosed();
421             return mWidth;
422         }
423 
424         /**
425          * Returns the height of the given {@link Page} object in points (1/72"). It is not
426          * guaranteed that all pages will have the same height and the viewport should be resized to
427          * the given page height.
428          *
429          * @return height of the given page
430          * @throws IllegalStateException If the document/page is closed before invocation.
431          */
432         @IntRange(from = 0)
getHeight()433         public int getHeight() {
434             throwIfDocumentOrPageClosed();
435             return mHeight;
436         }
437 
438         /**
439          * Search for the given string on the page and returns the bounds of all the matches. The
440          * list will be empty if there are no matches on the given page. If this function was
441          * invoked previously for any page, it will wait for that operation to
442          * complete before this operation is started.
443          * <p>
444          * <strong>Note:</strong> Should be invoked on the {@link android.annotation.WorkerThread}
445          * as it is long-running task.
446          *
447          * @param query plain search string for querying the document
448          * @return List of {@link PageMatchBounds} representing the bounds of each match on the
449          * page.
450          * @throws IllegalStateException If the document/page is closed before invocation.
451          */
452         @NonNull
searchText(@onNull String query)453         public List<PageMatchBounds> searchText(@NonNull String query) {
454             throwIfDocumentOrPageClosed();
455             return mPdfProcessor.searchPageText(mIndex, query);
456         }
457 
458         /**
459          * Return a {@link PageSelection} which represents the selected content that spans between
460          * the two boundaries. The boundaries can be either exactly defined with text indexes, or
461          * approximately defined with points on the page. The resulting selection will also be
462          * exactly defined with both indexes and points. If the start and stop boundary are both at
463          * the same point, selects the word at that point. In case the selection from the given
464          * boundaries result in an empty space, then the method returns {@code null}. The start and
465          * stop {@link SelectionBoundary} in {@link PageSelection} resolves to the "nearest" index
466          * when returned.
467          * <p>
468          * <strong>Note:</strong> Should be invoked on a {@link android.annotation.WorkerThread}
469          * as it is long-running task.
470          *
471          * @param start boundary where the selection starts (inclusive)
472          * @param stop  boundary where the selection stops (exclusive)
473          * @return collection of the selected content for text, images, etc.
474          * @throws IllegalStateException If the document/page is closed before invocation.
475          */
476         @Nullable
selectContent(@onNull SelectionBoundary start, @NonNull SelectionBoundary stop)477         public PageSelection selectContent(@NonNull SelectionBoundary start,
478                 @NonNull SelectionBoundary stop) {
479             throwIfDocumentOrPageClosed();
480             return mPdfProcessor.selectPageText(mIndex, start, stop);
481         }
482 
483 
484         /**
485          * Get the bounds and URLs of all the links on the given page.
486          *
487          * @return list of all links on the page.
488          * @throws IllegalStateException If the document/page is closed before invocation.
489          */
490         @NonNull
getLinkContents()491         public List<PdfPageLinkContent> getLinkContents() {
492             throwIfDocumentOrPageClosed();
493             return mPdfProcessor.getPageLinkContents(mIndex);
494         }
495 
496         /**
497          * Gets bookmarks and goto links present on the page of a pdf document. Goto Links
498          * are the internal navigation links which directs the user to different location
499          * within the same document.
500          *
501          * @return list of all goto links {@link PdfPageGotoLinkContent} on a page, ordered
502          * left to right and top to bottom.
503          * @throws IllegalStateException If the document/page is closed before invocation.
504          */
505         @NonNull
getGotoLinks()506         public List<PdfPageGotoLinkContent> getGotoLinks() {
507             throwIfDocumentOrPageClosed();
508             return mPdfProcessor.getPageGotoLinks(mIndex);
509         }
510 
511 
512         /**
513          * Returns information about all form widgets on the page, or an empty list if there are no
514          * form widgets on the page.
515          *
516          * @throws IllegalStateException If the document is already closed.
517          * @throws IllegalStateException If the page is already closed.
518          */
519         @NonNull
520         @FlaggedApi(Flags.FLAG_ENABLE_FORM_FILLING)
getFormWidgetInfos()521         public List<FormWidgetInfo> getFormWidgetInfos() {
522             return getFormWidgetInfos(new int[0]);
523         }
524 
525         /**
526          * Returns information about all form widgets of the specified types on the page, or an
527          * empty list if there are no form widgets of the specified types on the page.
528          *
529          * @param types the types of form widgets to return, or an empty array to return all widgets
530          * @throws IllegalStateException If the document is already closed.
531          * @throws IllegalStateException If the page is already closed.
532          */
533         @NonNull
534         @FlaggedApi(Flags.FLAG_ENABLE_FORM_FILLING)
getFormWidgetInfos( @onNull @ormWidgetInfo.WidgetType int[] types)535         public List<FormWidgetInfo> getFormWidgetInfos(
536                 @NonNull @FormWidgetInfo.WidgetType int[] types) {
537             throwIfDocumentClosed();
538             throwIfPageClosed();
539             return mPdfProcessor.getFormWidgetInfos(mIndex, types);
540         }
541 
542         /**
543          * Returns information about the widget with {@code annotationIndex}.
544          *
545          * @param annotationIndex the index of the widget within the page's "Annot" array in the
546          *                        PDF document, available on results of previous calls to
547          *                        {@link #getFormWidgetInfos(int[])} or
548          *                        {@link #getFormWidgetInfoAtPosition(int, int)} via
549          *                        {@link FormWidgetInfo#getWidgetIndex()}.
550          * @throws IllegalArgumentException if there is no form widget at the provided index.
551          * @throws IllegalStateException    If the document is already closed.
552          * @throws IllegalStateException    If the page is already closed.
553          */
554         @NonNull
555         @FlaggedApi(Flags.FLAG_ENABLE_FORM_FILLING)
getFormWidgetInfoAtIndex(@ntRangefrom = 0) int annotationIndex)556         public FormWidgetInfo getFormWidgetInfoAtIndex(@IntRange(from = 0) int annotationIndex) {
557             throwIfDocumentClosed();
558             throwIfPageClosed();
559             return mPdfProcessor.getFormWidgetInfoAtIndex(mIndex, annotationIndex);
560         }
561 
562         /**
563          * Returns information about the widget at the given point.
564          *
565          * @param x the x position of the widget on the page, in points
566          * @param y the y position of the widget on the page, in points
567          * @throws IllegalArgumentException if there is no form widget at the provided position.
568          * @throws IllegalStateException    If the document is already closed.
569          * @throws IllegalStateException    If the page is already closed.
570          */
571         @NonNull
572         @FlaggedApi(Flags.FLAG_ENABLE_FORM_FILLING)
getFormWidgetInfoAtPosition(int x, int y)573         public FormWidgetInfo getFormWidgetInfoAtPosition(int x, int y) {
574             throwIfDocumentClosed();
575             throwIfPageClosed();
576             return mPdfProcessor.getFormWidgetInfoAtPosition(mIndex, x, y);
577         }
578 
579         /**
580          * Applies a {@link FormEditRecord} to the PDF.
581          *
582          * <p>Apps must call {@link #render(Bitmap, Rect, Matrix, RenderParams)} to render new
583          * bitmaps for the corresponding areas of the page.
584          *
585          * <p>For click type {@link FormEditRecord}s, performs a click on {@link
586          * FormEditRecord#getClickPoint()}
587          *
588          * <p>For set text type {@link FormEditRecord}s, sets the text value of the form widget.
589          *
590          * <p>For set indices type {@link FormEditRecord}s, sets the {@link
591          * FormEditRecord#getSelectedIndices()} as selected and all others as unselected for the
592          * form widget indicated by the record.
593          *
594          * @param editRecord the {@link FormEditRecord} to be applied
595          * @return Rectangular areas of the page bitmap that have been invalidated by this action.
596          * @throws IllegalArgumentException if the provided {@link FormEditRecord} cannot be
597          *                                  applied to the widget indicated by the index, or if the
598          *                                  index does not correspond to a widget on the page.
599          * @throws IllegalStateException    If the document is already closed.
600          * @throws IllegalStateException    If the page is already closed.
601          */
602         @android.annotation.NonNull
603         @FlaggedApi(Flags.FLAG_ENABLE_FORM_FILLING)
applyEdit(@onNull FormEditRecord editRecord)604         public List<Rect> applyEdit(@NonNull FormEditRecord editRecord) {
605             throwIfDocumentClosed();
606             throwIfPageClosed();
607             return mPdfProcessor.applyEdit(mIndex, editRecord);
608         }
609 
610         /**
611          * Closes this page.
612          *
613          * @see android.graphics.pdf.PdfRenderer#openPage(int)
614          */
615         @Override
close()616         public void close() {
617             throwIfDocumentOrPageClosed();
618             doClose();
619         }
620 
621         // SuppressLint: Finalize needs to be overridden to make sure all resources are closed
622         // gracefully.
623         @Override
624         @SuppressLint("GenericException")
finalize()625         protected void finalize() throws Throwable {
626             try {
627                 doClose();
628             } finally {
629                 super.finalize();
630             }
631         }
632 
doClose()633         private void doClose() {
634             if (mPdfProcessor != null) {
635                 mPdfProcessor.releasePage(mIndex);
636                 mIndex = -1;
637             }
638         }
639 
throwIfPageClosed()640         private void throwIfPageClosed() {
641             if (mIndex == -1) {
642                 throw new IllegalStateException("Page already closed!");
643             }
644         }
645 
throwIfDocumentOrPageClosed()646         private void throwIfDocumentOrPageClosed() {
647             throwIfDocumentClosed();
648             throwIfPageClosed();
649         }
650     }
651 }
652