Merge "Trim unnecessary pages when printing." into lmp-dev

This commit is contained in:
Svetoslav
2014-09-09 22:11:19 +00:00
committed by Android (Google) Code Review
13 changed files with 701 additions and 49 deletions

View File

@@ -129,6 +129,7 @@ LOCAL_SRC_FILES:= \
android/graphics/Xfermode.cpp \
android/graphics/YuvToJpegEncoder.cpp \
android/graphics/pdf/PdfDocument.cpp \
android/graphics/pdf/PdfEditor.cpp \
android/graphics/pdf/PdfRenderer.cpp \
android_media_AudioRecord.cpp \
android_media_AudioSystem.cpp \

View File

@@ -126,6 +126,7 @@ extern int register_android_graphics_Region(JNIEnv* env);
extern int register_android_graphics_SurfaceTexture(JNIEnv* env);
extern int register_android_graphics_Xfermode(JNIEnv* env);
extern int register_android_graphics_pdf_PdfDocument(JNIEnv* env);
extern int register_android_graphics_pdf_PdfEditor(JNIEnv* env);
extern int register_android_graphics_pdf_PdfRenderer(JNIEnv* env);
extern int register_android_view_DisplayEventReceiver(JNIEnv* env);
extern int register_android_view_RenderNode(JNIEnv* env);
@@ -1305,6 +1306,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_graphics_Xfermode),
REG_JNI(register_android_graphics_YuvImage),
REG_JNI(register_android_graphics_pdf_PdfDocument),
REG_JNI(register_android_graphics_pdf_PdfEditor),
REG_JNI(register_android_graphics_pdf_PdfRenderer),
REG_JNI(register_android_database_CursorWindow),

View File

@@ -0,0 +1,161 @@
/*
* 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.
*/
#include "jni.h"
#include "JNIHelp.h"
#include "fpdfview.h"
#include "fpdfedit.h"
#include "fpdfsave.h"
#include <android_runtime/AndroidRuntime.h>
#include <vector>
#include <utils/Log.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
namespace android {
static Mutex sLock;
static int sUnmatchedInitRequestCount = 0;
static void initializeLibraryIfNeeded() {
Mutex::Autolock _l(sLock);
if (sUnmatchedInitRequestCount == 0) {
FPDF_InitLibrary(NULL);
}
sUnmatchedInitRequestCount++;
}
static void destroyLibraryIfNeeded() {
Mutex::Autolock _l(sLock);
sUnmatchedInitRequestCount--;
if (sUnmatchedInitRequestCount == 0) {
FPDF_DestroyLibrary();
}
}
static int getBlock(void* param, unsigned long position, unsigned char* outBuffer,
unsigned long size) {
const int fd = reinterpret_cast<intptr_t>(param);
const int readCount = pread(fd, outBuffer, size, position);
if (readCount < 0) {
ALOGE("Cannot read from file descriptor. Error:%d", errno);
return 0;
}
return 1;
}
static jlong nativeOpen(JNIEnv* env, jclass thiz, jint fd, jlong size) {
initializeLibraryIfNeeded();
FPDF_FILEACCESS loader;
loader.m_FileLen = size;
loader.m_Param = reinterpret_cast<void*>(intptr_t(fd));
loader.m_GetBlock = &getBlock;
FPDF_DOCUMENT document = FPDF_LoadCustomDocument(&loader, NULL);
if (!document) {
const long error = FPDF_GetLastError();
jniThrowException(env, "java/io/IOException",
"cannot create document. Error:" + error);
destroyLibraryIfNeeded();
return -1;
}
return reinterpret_cast<jlong>(document);
}
static void nativeClose(JNIEnv* env, jclass thiz, jlong documentPtr) {
FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
FPDF_CloseDocument(document);
destroyLibraryIfNeeded();
}
static jint nativeGetPageCount(JNIEnv* env, jclass thiz, jlong documentPtr) {
FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
return FPDF_GetPageCount(document);
}
static jint nativeRemovePage(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex) {
FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
FPDFPage_Delete(document, pageIndex);
return FPDF_GetPageCount(document);
}
struct PdfToFdWriter : FPDF_FILEWRITE {
int dstFd;
};
static bool writeAllBytes(const int fd, const void* buffer, const size_t byteCount) {
char* writeBuffer = static_cast<char*>(const_cast<void*>(buffer));
size_t remainingBytes = byteCount;
while (remainingBytes > 0) {
ssize_t writtenByteCount = write(fd, writeBuffer, remainingBytes);
if (writtenByteCount == -1) {
if (errno == EINTR) {
continue;
}
__android_log_print(ANDROID_LOG_ERROR, LOG_TAG,
"Error writing to buffer: %d", errno);
return false;
}
remainingBytes -= writtenByteCount;
writeBuffer += writtenByteCount;
}
return true;
}
static int writeBlock(FPDF_FILEWRITE* owner, const void* buffer, unsigned long size) {
const PdfToFdWriter* writer = reinterpret_cast<PdfToFdWriter*>(owner);
const bool success = writeAllBytes(writer->dstFd, buffer, size);
if (success < 0) {
ALOGE("Cannot write to file descriptor. Error:%d", errno);
return 0;
}
return 1;
}
static void nativeWrite(JNIEnv* env, jclass thiz, jlong documentPtr, jint fd) {
FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
PdfToFdWriter writer;
writer.dstFd = fd;
writer.WriteBlock = &writeBlock;
const bool success = FPDF_SaveAsCopy(document, &writer, FPDF_NO_INCREMENTAL);
if (!success) {
jniThrowException(env, "java/io/IOException",
"cannot write to fd. Error:" + errno);
destroyLibraryIfNeeded();
}
}
static JNINativeMethod gPdfEditor_Methods[] = {
{"nativeOpen", "(IJ)J", (void*) nativeOpen},
{"nativeClose", "(J)V", (void*) nativeClose},
{"nativeGetPageCount", "(J)I", (void*) nativeGetPageCount},
{"nativeRemovePage", "(JI)I", (void*) nativeRemovePage},
{"nativeWrite", "(JI)V", (void*) nativeWrite}
};
int register_android_graphics_pdf_PdfEditor(JNIEnv* env) {
return android::AndroidRuntime::registerNativeMethods(
env, "android/graphics/pdf/PdfEditor", gPdfEditor_Methods,
NELEM(gPdfEditor_Methods));
};
};

View File

@@ -0,0 +1,162 @@
/*
* 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 android.graphics.pdf;
import android.annotation.NonNull;
import android.os.ParcelFileDescriptor;
import android.system.ErrnoException;
import android.system.OsConstants;
import dalvik.system.CloseGuard;
import libcore.io.IoUtils;
import libcore.io.Libcore;
import java.io.IOException;
/**
* Class for editing PDF files.
*
* @hide
*/
public final class PdfEditor {
private final CloseGuard mCloseGuard = CloseGuard.get();
private final long mNativeDocument;
private int mPageCount;
private ParcelFileDescriptor mInput;
/**
* Creates a new instance.
* <p>
* <strong>Note:</strong> The provided file descriptor must be <strong>seekable</strong>,
* i.e. its data being randomly accessed, e.g. pointing to a file. After finishing
* with this class you must call {@link #close()}.
* </p>
* <p>
* <strong>Note:</strong> This class takes ownership of the passed in file descriptor
* and is responsible for closing it when the editor is closed.
* </p>
*
* @param input Seekable file descriptor to read from.
*
* @see #close()
*/
public PdfEditor(@NonNull ParcelFileDescriptor input) throws IOException {
if (input == null) {
throw new NullPointerException("input cannot be null");
}
final long size;
try {
Libcore.os.lseek(input.getFileDescriptor(), 0, OsConstants.SEEK_SET);
size = Libcore.os.fstat(input.getFileDescriptor()).st_size;
} catch (ErrnoException ee) {
throw new IllegalArgumentException("file descriptor not seekable");
}
mInput = input;
mNativeDocument = nativeOpen(mInput.getFd(), size);
mPageCount = nativeGetPageCount(mNativeDocument);
mCloseGuard.open("close");
}
/**
* Gets the number of pages in the document.
*
* @return The page count.
*/
public int getPageCount() {
throwIfClosed();
return mPageCount;
}
/**
* Removes the page with a given index.
*
* @param pageIndex The page to remove.
*/
public void removePage(int pageIndex) {
throwIfClosed();
throwIfPageNotInDocument(pageIndex);
mPageCount = nativeRemovePage(mNativeDocument, pageIndex);
}
/**
* Writes the PDF file to the provided destination.
* <p>
* <strong>Note:</strong> This method takes ownership of the passed in file
* descriptor and is responsible for closing it when writing completes.
* </p>
* @param output The destination.
*/
public void write(ParcelFileDescriptor output) throws IOException {
try {
throwIfClosed();
nativeWrite(mNativeDocument, output.getFd());
} finally {
IoUtils.closeQuietly(output);
}
}
/**
* Closes this editor. You should not use this instance
* after this method is called.
*/
public void close() {
throwIfClosed();
doClose();
}
@Override
protected void finalize() throws Throwable {
try {
mCloseGuard.warnIfOpen();
if (mInput != null) {
doClose();
}
} finally {
super.finalize();
}
}
private void doClose() {
nativeClose(mNativeDocument);
IoUtils.closeQuietly(mInput);
mInput = null;
mCloseGuard.close();
}
private void throwIfClosed() {
if (mInput == null) {
throw new IllegalStateException("Already closed");
}
}
private void throwIfPageNotInDocument(int pageIndex) {
if (pageIndex < 0 || pageIndex >= mPageCount) {
throw new IllegalArgumentException("Invalid page index");
}
}
private static native long nativeOpen(int fd, long size);
private static native void nativeClose(long documentPtr);
private static native int nativeGetPageCount(long documentPtr);
private static native int nativeRemovePage(long documentPtr, int pageIndex);
private static native void nativeWrite(long documentPtr, int fd);
}

View File

@@ -239,7 +239,7 @@ public final class PdfRenderer implements AutoCloseable {
}
private void throwIfPageNotInDocument(int pageIndex) {
if (pageIndex >= mPageCount) {
if (pageIndex < 0 || pageIndex >= mPageCount) {
throw new IllegalArgumentException("Invalid page index");
}
}

View File

@@ -19,7 +19,9 @@ 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_SRC_FILES += \
src/com/android/printspooler/renderer/IPdfRenderer.aidl \
src/com/android/printspooler/renderer/IPdfEditor.aidl
LOCAL_PACKAGE_NAME := PrintSpooler

View File

@@ -54,7 +54,7 @@
</service>
<service
android:name=".renderer.PdfRendererService"
android:name=".renderer.PdfManipulationService"
android:isolatedProcess="true">
</service>

View File

@@ -37,7 +37,7 @@ 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 com.android.printspooler.renderer.PdfManipulationService;
import com.android.printspooler.util.BitmapSerializeUtils;
import dalvik.system.CloseGuard;
import libcore.io.IoUtils;
@@ -490,7 +490,8 @@ public final class PageContentRepository {
new AsyncTask<Void, Void, Integer>() {
@Override
protected void onPreExecute() {
Intent intent = new Intent(mContext, PdfRendererService.class);
Intent intent = new Intent(PdfManipulationService.ACTION_GET_RENDERER);
intent.setClass(mContext, PdfManipulationService.class);
mContext.bindService(intent, AsyncRenderer.this, Context.BIND_AUTO_CREATE);
}
@@ -555,7 +556,7 @@ public final class PageContentRepository {
callback.run();
}
}
}.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
}
public void destroy() {

View File

@@ -0,0 +1,30 @@
/*
* 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.os.ParcelFileDescriptor;
import android.print.PageRange;
/**
* Interface for communication with a remote pdf editor.
*/
interface IPdfEditor {
int openDocument(in ParcelFileDescriptor source);
void removePages(in PageRange[] pages);
void write(in ParcelFileDescriptor destination);
void closeDocument();
}

View File

@@ -29,5 +29,4 @@ interface IPdfRenderer {
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

@@ -23,6 +23,7 @@ import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.pdf.PdfEditor;
import android.graphics.pdf.PdfRenderer;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
@@ -32,15 +33,21 @@ import android.print.PrintAttributes;
import android.print.PrintAttributes.Margins;
import android.util.Log;
import android.view.View;
import com.android.printspooler.util.PageRangeUtils;
import libcore.io.IoUtils;
import com.android.printspooler.util.BitmapSerializeUtils;
import java.io.IOException;
/**
* Service for rendering PDF documents in an isolated process.
* Service for manipulation of PDF documents in an isolated process.
*/
public final class PdfRendererService extends Service {
private static final String LOG_TAG = "PdfRendererService";
public final class PdfManipulationService extends Service {
public static final String ACTION_GET_RENDERER =
"com.android.printspooler.renderer.ACTION_GET_RENDERER";
public static final String ACTION_GET_EDITOR =
"com.android.printspooler.renderer.ACTION_GET_EDITOR";
private static final String LOG_TAG = "PdfManipulationService";
private static final boolean DEBUG = false;
private static final int MILS_PER_INCH = 1000;
@@ -48,7 +55,18 @@ public final class PdfRendererService extends Service {
@Override
public IBinder onBind(Intent intent) {
return new PdfRendererImpl();
String action = intent.getAction();
switch (action) {
case ACTION_GET_RENDERER: {
return new PdfRendererImpl();
}
case ACTION_GET_EDITOR: {
return new PdfEditorImpl();
}
default: {
throw new IllegalArgumentException("Invalid intent action:" + action);
}
}
}
private final class PdfRendererImpl extends IPdfRenderer.Stub {
@@ -60,15 +78,17 @@ public final class PdfRendererService extends Service {
@Override
public int openDocument(ParcelFileDescriptor source) throws RemoteException {
synchronized (mLock) {
throwIfOpened();
if (DEBUG) {
Log.i(LOG_TAG, "openDocument()");
}
try {
throwIfOpened();
if (DEBUG) {
Log.i(LOG_TAG, "openDocument()");
}
mRenderer = new PdfRenderer(source);
return mRenderer.getPageCount();
} catch (IOException ioe) {
throw new RemoteException("Cannot open file");
} catch (IOException|IllegalStateException e) {
IoUtils.closeQuietly(source);
Log.e(LOG_TAG, "Cannot open file", e);
throw new RemoteException(e.toString());
}
}
}
@@ -108,7 +128,7 @@ public final class PdfRendererService extends Service {
}
matrix.postScale(displayScale, displayScale);
Configuration configuration = PdfRendererService.this.getResources()
Configuration configuration = PdfManipulationService.this.getResources()
.getConfiguration();
if (configuration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
matrix.postTranslate(bitmapWidth - srcWidthPts * displayScale, 0);
@@ -147,24 +167,13 @@ public final class PdfRendererService extends Service {
synchronized (mLock) {
throwIfNotOpened();
if (DEBUG) {
Log.i(LOG_TAG, "openDocument()");
Log.i(LOG_TAG, "closeDocument()");
}
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) {
@@ -191,6 +200,91 @@ public final class PdfRendererService extends Service {
}
}
private final class PdfEditorImpl extends IPdfEditor.Stub {
private final Object mLock = new Object();
private PdfEditor mEditor;
@Override
public int openDocument(ParcelFileDescriptor source) throws RemoteException {
synchronized (mLock) {
try {
throwIfOpened();
if (DEBUG) {
Log.i(LOG_TAG, "openDocument()");
}
mEditor = new PdfEditor(source);
return mEditor.getPageCount();
} catch (IOException|IllegalStateException e) {
IoUtils.closeQuietly(source);
Log.e(LOG_TAG, "Cannot open file", e);
throw new RemoteException(e.toString());
}
}
}
@Override
public void removePages(PageRange[] ranges) {
synchronized (mLock) {
throwIfNotOpened();
if (DEBUG) {
Log.i(LOG_TAG, "removePages()");
}
ranges = PageRangeUtils.normalize(ranges);
final int rangeCount = ranges.length;
for (int i = rangeCount - 1; i >= 0; i--) {
PageRange range = ranges[i];
for (int j = range.getEnd(); j >= range.getStart(); j--) {
mEditor.removePage(j);
}
}
}
}
@Override
public void write(ParcelFileDescriptor destination) throws RemoteException {
synchronized (mLock) {
try {
throwIfNotOpened();
if (DEBUG) {
Log.i(LOG_TAG, "write()");
}
mEditor.write(destination);
} catch (IOException | IllegalStateException e) {
IoUtils.closeQuietly(destination);
Log.e(LOG_TAG, "Error writing PDF to file.", e);
throw new RemoteException(e.toString());
}
}
}
@Override
public void closeDocument() {
synchronized (mLock) {
throwIfNotOpened();
if (DEBUG) {
Log.i(LOG_TAG, "closeDocument()");
}
mEditor.close();
mEditor = null;
}
}
private void throwIfOpened() {
if (mEditor != null) {
throw new IllegalStateException("Already opened");
}
}
private void throwIfNotOpened() {
if (mEditor == null) {
throw new IllegalStateException("Not opened");
}
}
}
private static int pointsFromMils(int mils) {
return (int) (((float) mils / MILS_PER_INCH) * POINTS_IN_INCH);
}

View File

@@ -23,6 +23,7 @@ import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
@@ -30,9 +31,12 @@ import android.content.res.Configuration;
import android.database.DataSetObserver;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.print.IPrintDocumentAdapter;
import android.print.PageRange;
import android.print.PrintAttributes;
@@ -74,6 +78,8 @@ import com.android.printspooler.model.PrintSpoolerProvider;
import com.android.printspooler.model.PrintSpoolerService;
import com.android.printspooler.model.RemotePrintDocument;
import com.android.printspooler.model.RemotePrintDocument.RemotePrintDocumentInfo;
import com.android.printspooler.renderer.IPdfEditor;
import com.android.printspooler.renderer.PdfManipulationService;
import com.android.printspooler.util.MediaSizeUtils;
import com.android.printspooler.util.MediaSizeUtils.MediaSizeComparator;
import com.android.printspooler.util.PageRangeUtils;
@@ -81,8 +87,15 @@ import com.android.printspooler.util.PrintOptionUtils;
import com.android.printspooler.widget.PrintContentView;
import com.android.printspooler.widget.PrintContentView.OptionsStateChangeListener;
import com.android.printspooler.widget.PrintContentView.OptionsStateController;
import libcore.io.IoUtils;
import libcore.io.Streams;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -186,6 +199,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
private ImageView mPrintButton;
private ProgressMessageController mProgressMessageController;
private MutexFileProvider mFileProvider;
private MediaSizeComparator mMediaSizeComparator;
@@ -257,9 +271,8 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
setTitle(R.string.print_dialog);
setContentView(R.layout.print_activity);
final MutexFileProvider fileProvider;
try {
fileProvider = new MutexFileProvider(
mFileProvider = new MutexFileProvider(
PrintSpoolerService.generateFileForPrintJob(
PrintActivity.this, mPrintJob.getId()));
} catch (IOException ioe) {
@@ -268,12 +281,13 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
}
mPrintPreviewController = new PrintPreviewController(PrintActivity.this,
fileProvider);
mFileProvider);
mPrintedDocument = new RemotePrintDocument(PrintActivity.this,
IPrintDocumentAdapter.Stub.asInterface(documentAdapter),
fileProvider, new RemotePrintDocument.DocumentObserver() {
mFileProvider, new RemotePrintDocument.DocumentObserver() {
@Override
public void onDestroy() {
setState(STATE_PRINT_CANCELED);
finish();
}
}, PrintActivity.this);
@@ -369,7 +383,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
@Override
public void onRequestContentUpdate() {
if (canUpdateDocument()) {
updateDocument(true, false);
updateDocument(false);
}
}
@@ -386,7 +400,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
@Override
public void onActionPerformed() {
if (mState == STATE_UPDATE_FAILED
&& canUpdateDocument() && updateDocument(true, true)) {
&& canUpdateDocument() && updateDocument(true)) {
ensurePreviewUiShown();
setState(STATE_CONFIGURING);
updateOptionsUi();
@@ -557,14 +571,13 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
if (resultCode == RESULT_OK && data != null) {
setState(STATE_PRINT_COMPLETED);
updateOptionsUi();
Uri uri = data.getData();
mPrintedDocument.writeContent(getContentResolver(), uri);
final Uri uri = data.getData();
// Calling finish here does not invoke lifecycle callbacks but we
// update the print job in onPause if finishing, hence post a message.
mDestinationSpinner.post(new Runnable() {
@Override
public void run() {
finish();
shredPagesAndFinish(uri);
}
});
} else if (resultCode == RESULT_CANCELED) {
@@ -708,7 +721,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
// Update the content if needed.
if (canUpdateDocument()) {
updateDocument(true, false);
updateDocument(false);
}
}
@@ -840,7 +853,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
if (mCurrentPrinter == mDestinationSpinnerAdapter.getPdfPrinter()) {
startCreateDocumentActivity();
} else {
finish();
shredPagesAndFinish(null);
}
}
@@ -893,7 +906,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
attributes.setMinMargins(defaults.getMinMargins());
}
private boolean updateDocument(boolean preview, boolean clearLastError) {
private boolean updateDocument(boolean clearLastError) {
if (!clearLastError && mPrintedDocument.hasUpdateError()) {
return false;
}
@@ -902,6 +915,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
mPrintedDocument.clearUpdateError();
}
final boolean preview = mState != STATE_PRINT_CONFIRMED;
final PageRange[] pages;
if (preview) {
pages = mPrintPreviewController.getRequestedPages();
@@ -959,7 +973,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
mPrintPreviewController.closeOptions();
if (canUpdateDocument()) {
updateDocument(false, false);
updateDocument(false);
}
if (!mPrintedDocument.isUpdating()) {
@@ -1433,7 +1447,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
if (mCurrentPrinter.equals(printer)) {
setState(STATE_CONFIGURING);
if (canUpdateDocument()) {
updateDocument(true, false);
updateDocument(false);
}
ensurePreviewUiShown();
updateOptionsUi();
@@ -1492,6 +1506,18 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
return true;
}
private void shredPagesAndFinish(final Uri writeToUri) {
new PageShredder(this, mPrintJob, mFileProvider, new Runnable() {
@Override
public void run() {
if (writeToUri != null) {
mPrintedDocument.writeContent(getContentResolver(), writeToUri);
}
finish();
}
}).shred();
}
private final class SpinnerItem<T> {
final T value;
final CharSequence label;
@@ -1942,7 +1968,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
|| (becameActive && hasCapab) || (isActive && gotCapab));
if (updateNeeded && canUpdateDocument()) {
updateDocument(true, false);
updateDocument(false);
}
updateOptionsUi();
@@ -2033,7 +2059,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
}
if (canUpdateDocument()) {
updateDocument(true, false);
updateDocument(false);
}
updateOptionsUi();
@@ -2158,7 +2184,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
updateOptionsUi();
if (hadErrors && canUpdateDocument()) {
updateDocument(true, false);
updateDocument(false);
}
}
}
@@ -2198,4 +2224,176 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
updateOptionsUi();
}
}
private static final class PageShredder implements ServiceConnection {
private static final String TEMP_FILE_PREFIX = "print_job";
private static final String TEMP_FILE_EXTENSION = ".pdf";
private final Context mContext;
private final MutexFileProvider mFileProvider;
private final PrintJobInfo mPrintJob;
private final PageRange[] mPagesToShred;
private final Runnable mCallback;
public PageShredder(Context context, PrintJobInfo printJob,
MutexFileProvider fileProvider, Runnable callback) {
mContext = context;
mPrintJob = printJob;
mFileProvider = fileProvider;
mCallback = callback;
mPagesToShred = computePagesToShred(mPrintJob);
}
public void shred() {
// If we have only the pages we want, done.
if (mPagesToShred.length <= 0) {
mCallback.run();
return;
}
// Bind to the manipulation service and the work
// will be performed upon connection to the service.
Intent intent = new Intent(PdfManipulationService.ACTION_GET_EDITOR);
intent.setClass(mContext, PdfManipulationService.class);
mContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
final IPdfEditor editor = IPdfEditor.Stub.asInterface(service);
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
try {
// It's OK to access the data members as they are
// final and this code is the last one to touch
// them as shredding is the very last step, so the
// UI is not interactive at this point.
shredPages(editor);
updatePrintJob();
} finally {
mContext.unbindService(PageShredder.this);
mCallback.run();
}
return null;
}
}.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
}
@Override
public void onServiceDisconnected(ComponentName name) {
/* do nothing */
}
private void shredPages(IPdfEditor editor) {
File tempFile = null;
ParcelFileDescriptor src = null;
ParcelFileDescriptor dst = null;
InputStream in = null;
OutputStream out = null;
try {
File jobFile = mFileProvider.acquireFile(null);
src = ParcelFileDescriptor.open(jobFile, ParcelFileDescriptor.MODE_READ_WRITE);
// Open the document.
editor.openDocument(src);
// We passed the fd over IPC, close this one.
src.close();
// Drop the pages.
editor.removePages(mPagesToShred);
// Write the modified PDF to a temp file.
tempFile = File.createTempFile(TEMP_FILE_PREFIX, TEMP_FILE_EXTENSION,
mContext.getCacheDir());
dst = ParcelFileDescriptor.open(tempFile, ParcelFileDescriptor.MODE_READ_WRITE);
editor.write(dst);
dst.close();
// Close the document.
editor.closeDocument();
// Copy the temp file over the print job file.
jobFile.delete();
in = new FileInputStream(tempFile);
out = new FileOutputStream(jobFile);
Streams.copy(in, out);
} catch (IOException|RemoteException e) {
Log.e(LOG_TAG, "Error dropping pages", e);
} finally {
IoUtils.closeQuietly(src);
IoUtils.closeQuietly(dst);
IoUtils.closeQuietly(in);
IoUtils.closeQuietly(out);
if (tempFile != null) {
tempFile.delete();
}
}
}
private void updatePrintJob() {
// Update the print job pages.
final int newPageCount = PageRangeUtils.getNormalizedPageCount(
mPrintJob.getPages(), 0);
mPrintJob.setPages(new PageRange[]{PageRange.ALL_PAGES});
// Update the print job document info.
PrintDocumentInfo oldDocInfo = mPrintJob.getDocumentInfo();
PrintDocumentInfo newDocInfo = new PrintDocumentInfo
.Builder(oldDocInfo.getName())
.setContentType(oldDocInfo.getContentType())
.setPageCount(newPageCount)
.build();
mPrintJob.setDocumentInfo(newDocInfo);
}
private static PageRange[] computePagesToShred(PrintJobInfo printJob) {
List<PageRange> rangesToShred = new ArrayList<>();
PageRange previousRange = null;
final int pageCount = printJob.getDocumentInfo().getPageCount();
PageRange[] printedPages = printJob.getPages();
final int rangeCount = printedPages.length;
for (int i = 0; i < rangeCount; i++) {
PageRange range = PageRangeUtils.asAbsoluteRange(printedPages[i], pageCount);
if (previousRange == null) {
final int startPageIdx = 0;
final int endPageIdx = range.getStart() - 1;
if (startPageIdx <= endPageIdx) {
PageRange removedRange = new PageRange(startPageIdx, endPageIdx);
rangesToShred.add(removedRange);
}
} else {
final int startPageIdx = previousRange.getEnd() + 1;
final int endPageIdx = range.getStart() - 1;
if (startPageIdx <= endPageIdx) {
PageRange removedRange = new PageRange(startPageIdx, endPageIdx);
rangesToShred.add(removedRange);
}
}
if (i == rangeCount - 1) {
final int startPageIdx = range.getEnd() + 1;
final int endPageIdx = printJob.getDocumentInfo().getPageCount() - 1;
if (startPageIdx <= endPageIdx) {
PageRange removedRange = new PageRange(startPageIdx, endPageIdx);
rangesToShred.add(removedRange);
}
}
previousRange = range;
}
PageRange[] result = new PageRange[rangesToShred.size()];
rangesToShred.toArray(result);
return result;
}
}
}

View File

@@ -185,8 +185,10 @@ class PrintPreviewController implements MutexFileProvider.OnReleaseRequestCallba
public void run() {
// At this point the other end will write to the file, hence
// we have to close it and reopen after the write completes.
Message operation = mHandler.obtainMessage(MyHandler.MSG_CLOSE);
mHandler.enqueueOperation(operation);
if (mPageAdapter.isOpened()) {
Message operation = mHandler.obtainMessage(MyHandler.MSG_CLOSE);
mHandler.enqueueOperation(operation);
}
}
});
}