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