Merge "Move print rendering in an isolated process." into lmp-dev

This commit is contained in:
Svet Ganov
2014-09-02 21:51:45 +00:00
committed by Android (Google) Code Review
9 changed files with 357 additions and 103 deletions

View File

@@ -195,6 +195,7 @@ public final class PdfRenderer implements AutoCloseable {
public Page openPage(int index) {
throwIfClosed();
throwIfPageOpened();
throwIfPageNotInDocument(index);
mCurrentPage = new Page(index);
return mCurrentPage;
}
@@ -237,6 +238,12 @@ public final class PdfRenderer implements AutoCloseable {
}
}
private void throwIfPageNotInDocument(int pageIndex) {
if (pageIndex >= mPageCount) {
throw new IllegalArgumentException("Invalid page index");
}
}
/**
* This class represents a PDF document page for rendering.
*/

View File

@@ -19,6 +19,7 @@ include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SRC_FILES += src/com/android/printspooler/renderer/IPdfRenderer.aidl
LOCAL_PACKAGE_NAME := PrintSpooler

View File

@@ -53,6 +53,11 @@
android:permission="android.permission.BIND_PRINT_SPOOLER_SERVICE">
</service>
<service
android:name=".renderer.PdfRendererService"
android:isolatedProcess="true">
</service>
<activity
android:name=".ui.PrintActivity"
android:configChanges="orientation|screenSize"

View File

@@ -17,24 +17,31 @@
package com.android.printspooler.model;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Configuration;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.pdf.PdfRenderer;
import android.os.AsyncTask;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.print.PrintAttributes.MediaSize;
import android.print.PrintAttributes.Margins;
import android.os.RemoteException;
import android.print.PrintAttributes;
import android.print.PrintDocumentInfo;
import android.util.ArrayMap;
import android.util.Log;
import android.view.View;
import com.android.internal.annotations.GuardedBy;
import com.android.printspooler.renderer.IPdfRenderer;
import com.android.printspooler.renderer.PdfRendererService;
import dalvik.system.CloseGuard;
import libcore.io.IoUtils;
import java.io.FileDescriptor;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedHashMap;
@@ -55,9 +62,6 @@ public final class PageContentRepository {
private static final int BYTES_PER_MEGABYTE = 1048576;
private static final int MILS_PER_INCH = 1000;
private static final int POINTS_IN_INCH = 72;
private final CloseGuard mCloseGuard = CloseGuard.get();
private final ArrayMap<Integer, PageContentProvider> mPageContentProviders =
@@ -115,7 +119,6 @@ public final class PageContentRepository {
if (DEBUG) {
Log.i(LOG_TAG, "STATE_DESTROYED");
}
throwIfNotClosed();
doDestroy();
}
@@ -351,15 +354,13 @@ public final class PageContentRepository {
public static final class RenderSpec {
final int bitmapWidth;
final int bitmapHeight;
final MediaSize mediaSize;
final Margins minMargins;
final PrintAttributes printAttributes;
public RenderSpec(int bitmapWidth, int bitmapHeight,
MediaSize mediaSize, Margins minMargins) {
PrintAttributes printAttributes) {
this.bitmapWidth = bitmapWidth;
this.bitmapHeight = bitmapHeight;
this.mediaSize = mediaSize;
this.minMargins = minMargins;
this.printAttributes = printAttributes;
}
@Override
@@ -380,18 +381,11 @@ public final class PageContentRepository {
if (bitmapWidth != other.bitmapWidth) {
return false;
}
if (mediaSize != null) {
if (!mediaSize.equals(other.mediaSize)) {
if (printAttributes != null) {
if (!printAttributes.equals(other.printAttributes)) {
return false;
}
} else if (other.mediaSize != null) {
return false;
}
if (minMargins != null) {
if (!minMargins.equals(other.minMargins)) {
return false;
}
} else if (other.minMargins != null) {
} else if (other.printAttributes != null) {
return false;
}
return true;
@@ -407,8 +401,7 @@ public final class PageContentRepository {
public int hashCode() {
int result = bitmapWidth;
result = 31 * result + bitmapHeight;
result = 31 * result + (mediaSize != null ? mediaSize.hashCode() : 0);
result = 31 * result + (minMargins != null ? minMargins.hashCode() : 0);
result = 31 * result + (printAttributes != null ? printAttributes.hashCode() : 0);
return result;
}
}
@@ -440,13 +433,11 @@ public final class PageContentRepository {
}
}
private static int pointsFromMils(int mils) {
return (int) (((float) mils / MILS_PER_INCH) * POINTS_IN_INCH);
}
private static class AsyncRenderer {
private static final class AsyncRenderer implements ServiceConnection {
private static final int MALFORMED_PDF_FILE_ERROR = -2;
private final Object mLock = new Object();
private final Context mContext;
private final PageContentLruCache mPageContentCache;
@@ -457,8 +448,8 @@ public final class PageContentRepository {
private int mPageCount = PrintDocumentInfo.PAGE_COUNT_UNKNOWN;
// Accessed only by the executor thread.
private PdfRenderer mRenderer;
@GuardedBy("mLock")
private IPdfRenderer mRenderer;
public AsyncRenderer(Context context, OnMalformedPdfFileListener malformedPdfFileListener) {
mContext = context;
@@ -470,6 +461,21 @@ public final class PageContentRepository {
mPageContentCache = new PageContentLruCache(cacheSizeInBytes);
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mLock) {
mRenderer = IPdfRenderer.Stub.asInterface(service);
mLock.notifyAll();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
synchronized (mLock) {
mRenderer = null;
}
}
public void open(final ParcelFileDescriptor source, final Runnable callback) {
// Opening a new document invalidates the cache as it has pages
// from the last document. We keep the cache even when the document
@@ -478,14 +484,28 @@ public final class PageContentRepository {
mPageContentCache.invalidate();
new AsyncTask<Void, Void, Integer>() {
@Override
protected void onPreExecute() {
Intent intent = new Intent(mContext, PdfRendererService.class);
mContext.bindService(intent, AsyncRenderer.this, Context.BIND_AUTO_CREATE);
}
@Override
protected Integer doInBackground(Void... params) {
try {
mRenderer = new PdfRenderer(source);
return mRenderer.getPageCount();
} catch (IOException ioe) {
Log.e(LOG_TAG, "Cannot open PDF document");
return MALFORMED_PDF_FILE_ERROR;
synchronized (mLock) {
while (mRenderer == null) {
try {
mLock.wait();
} catch (InterruptedException ie) {
/* ignore */
}
}
try {
return mRenderer.openDocument(source);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Cannot open PDF document");
return MALFORMED_PDF_FILE_ERROR;
}
}
}
@@ -510,7 +530,13 @@ public final class PageContentRepository {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
mRenderer.close();
synchronized (mLock) {
try {
mRenderer.closeDocument();
} catch (RemoteException re) {
/* ignore */
}
}
return null;
}
@@ -525,8 +551,19 @@ public final class PageContentRepository {
}
public void destroy() {
mPageContentCache.invalidate();
mPageContentCache.clear();
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
return null;
}
@Override
public void onPostExecute(Void result) {
mContext.unbindService(AsyncRenderer.this);
mPageContentCache.invalidate();
mPageContentCache.clear();
}
}.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
}
public void startPreload(int firstShownPage, int lastShownPage, RenderSpec renderSpec) {
@@ -752,62 +789,30 @@ public final class PageContentRepository {
return mRenderedPage;
}
PdfRenderer.Page page = mRenderer.openPage(mPageIndex);
if (isCancelled()) {
page.close();
return mRenderedPage;
}
Bitmap bitmap = mRenderedPage.content.getBitmap();
final int srcWidthPts = page.getWidth();
final int srcHeightPts = page.getHeight();
ParcelFileDescriptor[] pipe = null;
try {
pipe = ParcelFileDescriptor.createPipe();
ParcelFileDescriptor source = pipe[0];
ParcelFileDescriptor destination = pipe[1];
final int dstWidthPts = pointsFromMils(mRenderSpec.mediaSize.getWidthMils());
final int dstHeightPts = pointsFromMils(mRenderSpec.mediaSize.getHeightMils());
mRenderer.renderPage(mPageIndex, bitmap.getWidth(), bitmap.getHeight(),
mRenderSpec.printAttributes, destination);
final boolean scaleContent = mRenderer.shouldScaleForPrinting();
final boolean contentLandscape = !mRenderSpec.mediaSize.isPortrait();
// We passed the file descriptor to the other side which took
// ownership, so close our copy for the write to complete.
destination.close();
final float displayScale;
Matrix matrix = new Matrix();
if (scaleContent) {
displayScale = Math.min((float) bitmap.getWidth() / srcWidthPts,
(float) bitmap.getHeight() / srcHeightPts);
} else {
if (contentLandscape) {
displayScale = (float) bitmap.getHeight() / dstHeightPts;
} else {
displayScale = (float) bitmap.getWidth() / dstWidthPts;
}
BitmapFactory.Options options = new BitmapFactory.Options();
options.inBitmap = bitmap;
BitmapFactory.decodeFileDescriptor(source.getFileDescriptor(), null, options);
} catch (IOException|RemoteException e) {
Log.e(LOG_TAG, "Error rendering page:" + mPageIndex, e);
} finally {
IoUtils.closeQuietly(pipe[0]);
IoUtils.closeQuietly(pipe[1]);
}
matrix.postScale(displayScale, displayScale);
Configuration configuration = mContext.getResources().getConfiguration();
if (configuration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
matrix.postTranslate(bitmap.getWidth() - srcWidthPts * displayScale, 0);
}
final int paddingLeftPts = pointsFromMils(mRenderSpec.minMargins.getLeftMils());
final int paddingTopPts = pointsFromMils(mRenderSpec.minMargins.getTopMils());
final int paddingRightPts = pointsFromMils(mRenderSpec.minMargins.getRightMils());
final int paddingBottomPts = pointsFromMils(mRenderSpec.minMargins.getBottomMils());
Rect clip = new Rect();
clip.left = (int) (paddingLeftPts * displayScale);
clip.top = (int) (paddingTopPts * displayScale);
clip.right = (int) (bitmap.getWidth() - paddingRightPts * displayScale);
clip.bottom = (int) (bitmap.getHeight() - paddingBottomPts * displayScale);
if (DEBUG) {
Log.i(LOG_TAG, "Rendering page:" + mPageIndex + " of " + mPageCount);
}
page.render(bitmap, clip, matrix, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
page.close();
return mRenderedPage;
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.printspooler.renderer;
import android.graphics.Rect;
import android.os.ParcelFileDescriptor;
import android.print.PageRange;
import android.print.PrintAttributes;
/**
* Interface for communication with a remote pdf renderer.
*/
interface IPdfRenderer {
int openDocument(in ParcelFileDescriptor source);
oneway void renderPage(int pageIndex, int bitmapWidth, int bitmapHeight,
in PrintAttributes attributes, in ParcelFileDescriptor destination);
oneway void closeDocument();
oneway void writePages(in PageRange[] pages);
}

View File

@@ -0,0 +1,198 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.printspooler.renderer;
import android.app.Service;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.pdf.PdfRenderer;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.print.PageRange;
import android.print.PrintAttributes;
import android.print.PrintAttributes.Margins;
import android.util.Log;
import android.view.View;
import libcore.io.IoUtils;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* Service for rendering PDF documents in an isolated process.
*/
public final class PdfRendererService extends Service {
private static final String LOG_TAG = "PdfRendererService";
private static final boolean DEBUG = false;
private static final int MILS_PER_INCH = 1000;
private static final int POINTS_IN_INCH = 72;
@Override
public IBinder onBind(Intent intent) {
return new PdfRendererImpl();
}
private final class PdfRendererImpl extends IPdfRenderer.Stub {
private final Object mLock = new Object();
private Bitmap mBitmap;
private PdfRenderer mRenderer;
@Override
public int openDocument(ParcelFileDescriptor source) throws RemoteException {
synchronized (mLock) {
throwIfOpened();
if (DEBUG) {
Log.i(LOG_TAG, "openDocument()");
}
try {
mRenderer = new PdfRenderer(source);
return mRenderer.getPageCount();
} catch (IOException ioe) {
throw new RemoteException("Cannot open file");
}
}
}
@Override
public void renderPage(int pageIndex, int bitmapWidth, int bitmapHeight,
PrintAttributes attributes, ParcelFileDescriptor destination) {
FileOutputStream out = null;
synchronized (mLock) {
try {
throwIfNotOpened();
PdfRenderer.Page page = mRenderer.openPage(pageIndex);
final int srcWidthPts = page.getWidth();
final int srcHeightPts = page.getHeight();
final int dstWidthPts = pointsFromMils(
attributes.getMediaSize().getWidthMils());
final int dstHeightPts = pointsFromMils(
attributes.getMediaSize().getHeightMils());
final boolean scaleContent = mRenderer.shouldScaleForPrinting();
final boolean contentLandscape = !attributes.getMediaSize().isPortrait();
final float displayScale;
Matrix matrix = new Matrix();
if (scaleContent) {
displayScale = Math.min((float) bitmapWidth / srcWidthPts,
(float) bitmapHeight / srcHeightPts);
} else {
if (contentLandscape) {
displayScale = (float) bitmapHeight / dstHeightPts;
} else {
displayScale = (float) bitmapWidth / dstWidthPts;
}
}
matrix.postScale(displayScale, displayScale);
Configuration configuration = PdfRendererService.this.getResources()
.getConfiguration();
if (configuration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
matrix.postTranslate(bitmapWidth - srcWidthPts * displayScale, 0);
}
Margins minMargins = attributes.getMinMargins();
final int paddingLeftPts = pointsFromMils(minMargins.getLeftMils());
final int paddingTopPts = pointsFromMils(minMargins.getTopMils());
final int paddingRightPts = pointsFromMils(minMargins.getRightMils());
final int paddingBottomPts = pointsFromMils(minMargins.getBottomMils());
Rect clip = new Rect();
clip.left = (int) (paddingLeftPts * displayScale);
clip.top = (int) (paddingTopPts * displayScale);
clip.right = (int) (bitmapWidth - paddingRightPts * displayScale);
clip.bottom = (int) (bitmapHeight - paddingBottomPts * displayScale);
if (DEBUG) {
Log.i(LOG_TAG, "Rendering page:" + pageIndex);
}
Bitmap bitmap = getBitmapForSize(bitmapWidth, bitmapHeight);
page.render(bitmap, clip, matrix, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
page.close();
out = new FileOutputStream(destination.getFileDescriptor());
bitmap.compress(Bitmap.CompressFormat.PNG, 0, out);
} finally {
IoUtils.closeQuietly(out);
IoUtils.closeQuietly(destination);
}
}
}
@Override
public void closeDocument() {
synchronized (mLock) {
throwIfNotOpened();
if (DEBUG) {
Log.i(LOG_TAG, "openDocument()");
}
mRenderer.close();
mRenderer = null;
}
}
@Override
public void writePages(PageRange[] pages) {
synchronized (mLock) {
throwIfNotOpened();
if (DEBUG) {
Log.i(LOG_TAG, "writePages()");
}
// TODO: Implement dropping undesired pages.
}
}
private Bitmap getBitmapForSize(int width, int height) {
if (mBitmap != null) {
if (mBitmap.getWidth() == width && mBitmap.getHeight() == height) {
return mBitmap;
}
mBitmap.recycle();
}
mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
return mBitmap;
}
private void throwIfOpened() {
if (mRenderer != null) {
throw new IllegalStateException("Already opened");
}
}
private void throwIfNotOpened() {
if (mRenderer == null) {
throw new IllegalStateException("Not opened");
}
}
}
private static int pointsFromMils(int mils) {
return (int) (((float) mils / MILS_PER_INCH) * POINTS_IN_INCH);
}
}

View File

@@ -319,6 +319,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
mProgressMessageController.cancel();
mPrinterRegistry.setTrackedPrinter(null);
mPrintPreviewController.destroy();
mSpoolerProvider.destroy();
mPrintedDocument.finish();
mPrintedDocument.destroy();
@@ -2185,4 +2186,4 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
updateOptionsUi();
}
}
}
}

View File

@@ -191,6 +191,9 @@ class PrintPreviewController implements MutexFileProvider.OnReleaseRequestCallba
}
public void destroy() {
if (mPageAdapter.isOpened()) {
mPageAdapter.close(null);
}
mPageAdapter.destroy();
}

View File

@@ -20,6 +20,7 @@ import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.print.PrintAttributes;
import android.print.PrintAttributes.MediaSize;
import android.print.PrintAttributes.Margins;
import android.util.AttributeSet;
@@ -38,13 +39,12 @@ import com.android.printspooler.model.PageContentRepository.RenderSpec;
public class PageContentView extends View
implements PageContentRepository.OnPageContentAvailableCallback {
private final PrintAttributes mAttributes = new PrintAttributes.Builder().build();
private final ColorDrawable mEmptyState;
private PageContentProvider mProvider;
private MediaSize mMediaSize;
private Margins mMinMargins;
private boolean mContentRequested;
public PageContentView(Context context, AttributeSet attrs) {
@@ -83,15 +83,17 @@ public class PageContentView extends View
}
public void init(PageContentProvider provider, MediaSize mediaSize, Margins minMargins) {
if (mProvider == provider
&& ((mMediaSize == null) ? mediaSize == null : mMediaSize.equals(mediaSize))
&& ((mMinMargins == null) ? minMargins == null : mMinMargins.equals(minMargins))) {
if (mProvider == null ? provider == null : mProvider.equals(provider)
&& ((mAttributes.getMediaSize() == null)
? mediaSize == null : mAttributes.getMediaSize().equals(mediaSize))
&& ((mAttributes.getMinMargins() == null)
? minMargins == null : mAttributes.getMinMargins().equals(minMargins))) {
return;
}
mProvider = provider;
mMediaSize = mediaSize;
mMinMargins = minMargins;
mAttributes.setMediaSize(mediaSize);
mAttributes.setMinMargins(minMargins);
mContentRequested = false;
// If there is no provider we want immediately to switch to
@@ -106,8 +108,7 @@ public class PageContentView extends View
private void requestPageContentIfNeeded() {
if (getWidth() > 0 && getHeight() > 0 && !mContentRequested && mProvider != null) {
mContentRequested = true;
mProvider.getPageContent(new RenderSpec(getWidth(), getHeight(), mMediaSize,
mMinMargins), this);
mProvider.getPageContent(new RenderSpec(getWidth(), getHeight(), mAttributes), this);
}
}
}