• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  * Copyright (C) 2015-2016 Mopria Alliance, Inc.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package com.android.bips.jni;
19 
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.ServiceConnection;
24 import android.graphics.Bitmap;
25 import android.os.IBinder;
26 import android.os.ParcelFileDescriptor;
27 import android.os.RemoteException;
28 import android.util.Log;
29 
30 import com.android.bips.render.IPdfRender;
31 import com.android.bips.render.PdfRenderService;
32 
33 import java.io.File;
34 import java.io.FileNotFoundException;
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.nio.ByteBuffer;
38 
39 /**
40  * Renders pages of a PDF into an RGB buffer. For security, relies on a remote rendering service.
41  */
42 public class PdfRender {
43     private static final String TAG = PdfRender.class.getSimpleName();
44     private static final boolean DEBUG = false;
45 
46     /** The current singleton instance */
47     private static final Object sLock = new Object();
48     private static PdfRender sInstance;
49 
50     private final Context mContext;
51     private final Intent mIntent;
52     private IPdfRender mService;
53     private String mCurrentFile;
54 
55     /**
56      * Returns the PdfRender singleton, creating it if necessary.
57      */
getInstance(Context context)58     public static PdfRender getInstance(Context context) {
59         // Native code might call this without a context
60         synchronized (sLock) {
61             if (sInstance == null && context != null) {
62                 sInstance = new PdfRender(context.getApplicationContext());
63             }
64 
65             return sInstance;
66         }
67     }
68 
69     private ServiceConnection mConnection = new ServiceConnection() {
70         public void onServiceConnected(ComponentName className, IBinder service) {
71             if (DEBUG) Log.d(TAG, "service connected");
72             mService = IPdfRender.Stub.asInterface(service);
73         }
74 
75         public void onServiceDisconnected(ComponentName className) {
76             Log.w(TAG, "PdfRender service unexpectedly disconnected, reconnecting");
77             mService = null;
78             mContext.bindService(mIntent, this, Context.BIND_AUTO_CREATE);
79         }
80     };
81 
PdfRender(Context context)82     private PdfRender(Context context) {
83         mContext = context;
84         mIntent = new Intent(context, PdfRenderService.class);
85         context.bindService(mIntent, mConnection, Context.BIND_AUTO_CREATE);
86     }
87 
88     /** Shut down the PDF renderer */
close()89     public void close() {
90         mContext.unbindService(mConnection);
91         mService = null;
92 
93         synchronized (sLock) {
94             sInstance = null;
95         }
96     }
97 
98     /**
99      * Opens the specified document, returning the page count or 0 on error. (Called by native
100      * code.)
101      */
openDocument(String fileName)102     public int openDocument(String fileName) {
103         if (DEBUG) Log.d(TAG, "openDocument() " + fileName);
104         if (mService == null) {
105             return 0;
106         }
107 
108         if (mCurrentFile != null && !mCurrentFile.equals(fileName)) {
109             closeDocument();
110         }
111 
112         try {
113             ParcelFileDescriptor pfd = ParcelFileDescriptor.open(new File(fileName),
114                     ParcelFileDescriptor.MODE_READ_ONLY);
115             return mService.openDocument(pfd);
116         } catch (RemoteException | FileNotFoundException ex) {
117             Log.w(TAG, "Failed to open " + fileName, ex);
118             return 0;
119         }
120     }
121 
122     /**
123      * Returns the size of the specified page or null on error. (Called by native code.)
124      * @param page 1-based page
125      * @return width and height of page in points (1/72")
126      */
getPageSize(int page)127     public SizeD getPageSize(int page) {
128         if (DEBUG) Log.d(TAG, "getPageSize() page=" + page);
129         if (mService == null) {
130             return null;
131         }
132 
133         try {
134             return mService.getPageSize(page - 1);
135         } catch (RemoteException | IllegalArgumentException ex) {
136             Log.w(TAG, "getPageWidth failed", ex);
137             return null;
138         }
139     }
140 
141     /**
142      * Renders the content of the page. (Called by native code.)
143      * @param page 1-based page
144      * @param y y-offset onto page
145      * @param width width of area to render
146      * @param height height of area to render
147      * @param zoomFactor zoom factor to use when rendering data
148      * @param target target byte buffer to fill with results
149      * @return true if rendering was successful
150      */
renderPageStripe(int page, int y, int width, int height, double zoomFactor, ByteBuffer target)151     public boolean renderPageStripe(int page, int y, int width, int height,
152             double zoomFactor, ByteBuffer target) {
153         if (DEBUG) {
154             Log.d(TAG, "renderPageStripe() page=" + page + " y=" + y + " w=" + width
155                     + " h=" + height + " zoom=" + zoomFactor);
156         }
157         if (mService == null) {
158             return false;
159         }
160 
161         try {
162             long start = System.currentTimeMillis();
163             ParcelFileDescriptor input = mService.renderPageStripe(page - 1, y, width, height,
164                     zoomFactor);
165 
166             // Copy received data into the ByteBuffer
167             int expectedSize = width * height * 3;
168             byte[] readBuffer = new byte[128 * 1024];
169             try (InputStream in = new ParcelFileDescriptor.AutoCloseInputStream(input)) {
170                 int length;
171                 while ((length = in.read(readBuffer, 0, readBuffer.length)) > 0) {
172                     target.put(readBuffer, 0, length);
173                 }
174             }
175             if (target.position() != expectedSize) {
176                 Log.w(TAG, "Render failed: expected " + target.position() + ", got "
177                         + expectedSize + " bytes");
178                 return false;
179             }
180 
181             if (DEBUG) Log.d(TAG, "Received (" + (System.currentTimeMillis() - start) + "ms)");
182             return true;
183         } catch (RemoteException | IOException | IllegalArgumentException | OutOfMemoryError ex) {
184             Log.w(TAG, "Render failed", ex);
185             return false;
186         }
187     }
188 
189     /**
190      * Renders the content of the page to a bitmap.
191      *
192      * @param page   1-based page
193      * @param width  width of area to render
194      * @param height height of area to render
195      * @return Bitmap if rendering was successful or null on error
196      */
renderPage(int page, int width, int height)197     public Bitmap renderPage(int page, int width, int height) {
198         if (DEBUG) {
199             Log.d(TAG, "renderPage() page=" + " w=" + width + " h=" + height);
200         }
201         if (mService == null || page < 1) {
202             return null;
203         }
204         try {
205             return mService.renderPage(page - 1, width, height);
206         } catch (RemoteException | IllegalArgumentException | OutOfMemoryError ex) {
207             Log.w(TAG, "Render failed", ex);
208             return null;
209         }
210     }
211 
212     /**
213      * Releases any open resources for the current document and page.
214      */
closeDocument()215     public void closeDocument() {
216         if (DEBUG) Log.d(TAG, "closeDocument()");
217         if (mService == null) {
218             return;
219         }
220 
221         try {
222             mService.closeDocument();
223         } catch (RemoteException ignored) {
224         }
225     }
226 }
227