PdfManipulationService.java revision 62ce332c141cf7bc7200c4c87d63e395874fc3ec
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 17package com.android.printspooler.renderer; 18 19import android.app.Service; 20import android.content.Intent; 21import android.content.res.Configuration; 22import android.graphics.Bitmap; 23import android.graphics.Color; 24import android.graphics.Matrix; 25import android.graphics.Rect; 26import android.graphics.pdf.PdfEditor; 27import android.graphics.pdf.PdfRenderer; 28import android.os.IBinder; 29import android.os.ParcelFileDescriptor; 30import android.os.RemoteException; 31import android.print.PageRange; 32import android.print.PrintAttributes; 33import android.print.PrintAttributes.Margins; 34import android.util.Log; 35import android.view.View; 36import com.android.printspooler.util.PageRangeUtils; 37import libcore.io.IoUtils; 38import com.android.printspooler.util.BitmapSerializeUtils; 39import java.io.IOException; 40 41/** 42 * Service for manipulation of PDF documents in an isolated process. 43 */ 44public final class PdfManipulationService extends Service { 45 public static final String ACTION_GET_RENDERER = 46 "com.android.printspooler.renderer.ACTION_GET_RENDERER"; 47 public static final String ACTION_GET_EDITOR = 48 "com.android.printspooler.renderer.ACTION_GET_EDITOR"; 49 50 private static final String LOG_TAG = "PdfManipulationService"; 51 private static final boolean DEBUG = false; 52 53 private static final int MILS_PER_INCH = 1000; 54 private static final int POINTS_IN_INCH = 72; 55 56 @Override 57 public IBinder onBind(Intent intent) { 58 String action = intent.getAction(); 59 switch (action) { 60 case ACTION_GET_RENDERER: { 61 return new PdfRendererImpl(); 62 } 63 case ACTION_GET_EDITOR: { 64 return new PdfEditorImpl(); 65 } 66 default: { 67 throw new IllegalArgumentException("Invalid intent action:" + action); 68 } 69 } 70 } 71 72 private final class PdfRendererImpl extends IPdfRenderer.Stub { 73 private final Object mLock = new Object(); 74 75 private Bitmap mBitmap; 76 private PdfRenderer mRenderer; 77 78 @Override 79 public int openDocument(ParcelFileDescriptor source) throws RemoteException { 80 synchronized (mLock) { 81 try { 82 throwIfOpened(); 83 if (DEBUG) { 84 Log.i(LOG_TAG, "openDocument()"); 85 } 86 mRenderer = new PdfRenderer(source); 87 return mRenderer.getPageCount(); 88 } catch (IOException|IllegalStateException e) { 89 IoUtils.closeQuietly(source); 90 Log.e(LOG_TAG, "Cannot open file", e); 91 throw new RemoteException(e.toString()); 92 } 93 } 94 } 95 96 @Override 97 public void renderPage(int pageIndex, int bitmapWidth, int bitmapHeight, 98 PrintAttributes attributes, ParcelFileDescriptor destination) { 99 synchronized (mLock) { 100 try { 101 throwIfNotOpened(); 102 103 PdfRenderer.Page page = mRenderer.openPage(pageIndex); 104 105 final int srcWidthPts = page.getWidth(); 106 final int srcHeightPts = page.getHeight(); 107 108 final int dstWidthPts = pointsFromMils( 109 attributes.getMediaSize().getWidthMils()); 110 final int dstHeightPts = pointsFromMils( 111 attributes.getMediaSize().getHeightMils()); 112 113 final boolean scaleContent = mRenderer.shouldScaleForPrinting(); 114 final boolean contentLandscape = !attributes.getMediaSize().isPortrait(); 115 116 final float displayScale; 117 Matrix matrix = new Matrix(); 118 119 if (scaleContent) { 120 displayScale = Math.min((float) bitmapWidth / srcWidthPts, 121 (float) bitmapHeight / srcHeightPts); 122 } else { 123 if (contentLandscape) { 124 displayScale = (float) bitmapHeight / dstHeightPts; 125 } else { 126 displayScale = (float) bitmapWidth / dstWidthPts; 127 } 128 } 129 matrix.postScale(displayScale, displayScale); 130 131 Configuration configuration = PdfManipulationService.this.getResources() 132 .getConfiguration(); 133 if (configuration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { 134 matrix.postTranslate(bitmapWidth - srcWidthPts * displayScale, 0); 135 } 136 137 Margins minMargins = attributes.getMinMargins(); 138 final int paddingLeftPts = pointsFromMils(minMargins.getLeftMils()); 139 final int paddingTopPts = pointsFromMils(minMargins.getTopMils()); 140 final int paddingRightPts = pointsFromMils(minMargins.getRightMils()); 141 final int paddingBottomPts = pointsFromMils(minMargins.getBottomMils()); 142 143 Rect clip = new Rect(); 144 clip.left = (int) (paddingLeftPts * displayScale); 145 clip.top = (int) (paddingTopPts * displayScale); 146 clip.right = (int) (bitmapWidth - paddingRightPts * displayScale); 147 clip.bottom = (int) (bitmapHeight - paddingBottomPts * displayScale); 148 149 if (DEBUG) { 150 Log.i(LOG_TAG, "Rendering page:" + pageIndex); 151 } 152 153 Bitmap bitmap = getBitmapForSize(bitmapWidth, bitmapHeight); 154 page.render(bitmap, clip, matrix, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY); 155 156 page.close(); 157 158 BitmapSerializeUtils.writeBitmapPixels(bitmap, destination); 159 } finally { 160 IoUtils.closeQuietly(destination); 161 } 162 } 163 } 164 165 @Override 166 public void closeDocument() { 167 synchronized (mLock) { 168 throwIfNotOpened(); 169 if (DEBUG) { 170 Log.i(LOG_TAG, "closeDocument()"); 171 } 172 mRenderer.close(); 173 mRenderer = null; 174 } 175 } 176 177 private Bitmap getBitmapForSize(int width, int height) { 178 if (mBitmap != null) { 179 if (mBitmap.getWidth() == width && mBitmap.getHeight() == height) { 180 mBitmap.eraseColor(Color.WHITE); 181 return mBitmap; 182 } 183 mBitmap.recycle(); 184 } 185 mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 186 mBitmap.eraseColor(Color.WHITE); 187 return mBitmap; 188 } 189 190 private void throwIfOpened() { 191 if (mRenderer != null) { 192 throw new IllegalStateException("Already opened"); 193 } 194 } 195 196 private void throwIfNotOpened() { 197 if (mRenderer == null) { 198 throw new IllegalStateException("Not opened"); 199 } 200 } 201 } 202 203 private final class PdfEditorImpl extends IPdfEditor.Stub { 204 private final Object mLock = new Object(); 205 206 private PdfEditor mEditor; 207 208 @Override 209 public int openDocument(ParcelFileDescriptor source) throws RemoteException { 210 synchronized (mLock) { 211 try { 212 throwIfOpened(); 213 if (DEBUG) { 214 Log.i(LOG_TAG, "openDocument()"); 215 } 216 mEditor = new PdfEditor(source); 217 return mEditor.getPageCount(); 218 } catch (IOException|IllegalStateException e) { 219 IoUtils.closeQuietly(source); 220 Log.e(LOG_TAG, "Cannot open file", e); 221 throw new RemoteException(e.toString()); 222 } 223 } 224 } 225 226 @Override 227 public void removePages(PageRange[] ranges) { 228 synchronized (mLock) { 229 throwIfNotOpened(); 230 if (DEBUG) { 231 Log.i(LOG_TAG, "removePages()"); 232 } 233 234 ranges = PageRangeUtils.normalize(ranges); 235 236 final int rangeCount = ranges.length; 237 for (int i = rangeCount - 1; i >= 0; i--) { 238 PageRange range = ranges[i]; 239 for (int j = range.getEnd(); j >= range.getStart(); j--) { 240 mEditor.removePage(j); 241 } 242 } 243 } 244 } 245 246 @Override 247 public void write(ParcelFileDescriptor destination) throws RemoteException { 248 synchronized (mLock) { 249 try { 250 throwIfNotOpened(); 251 if (DEBUG) { 252 Log.i(LOG_TAG, "write()"); 253 } 254 mEditor.write(destination); 255 } catch (IOException | IllegalStateException e) { 256 IoUtils.closeQuietly(destination); 257 Log.e(LOG_TAG, "Error writing PDF to file.", e); 258 throw new RemoteException(e.toString()); 259 } 260 } 261 } 262 263 @Override 264 public void closeDocument() { 265 synchronized (mLock) { 266 throwIfNotOpened(); 267 if (DEBUG) { 268 Log.i(LOG_TAG, "closeDocument()"); 269 } 270 mEditor.close(); 271 mEditor = null; 272 } 273 } 274 275 private void throwIfOpened() { 276 if (mEditor != null) { 277 throw new IllegalStateException("Already opened"); 278 } 279 } 280 281 private void throwIfNotOpened() { 282 if (mEditor == null) { 283 throw new IllegalStateException("Not opened"); 284 } 285 } 286 } 287 288 private static int pointsFromMils(int mils) { 289 return (int) (((float) mils / MILS_PER_INCH) * POINTS_IN_INCH); 290 } 291} 292