summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/jni/Android.mk1
-rw-r--r--core/jni/AndroidRuntime.cpp2
-rw-r--r--core/jni/android/graphics/pdf/PdfEditor.cpp161
-rw-r--r--graphics/java/android/graphics/pdf/PdfEditor.java162
-rw-r--r--graphics/java/android/graphics/pdf/PdfRenderer.java2
-rw-r--r--packages/PrintSpooler/Android.mk4
-rw-r--r--packages/PrintSpooler/AndroidManifest.xml2
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java7
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/renderer/IPdfEditor.aidl30
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/renderer/IPdfRenderer.aidl1
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/renderer/PdfManipulationService.java (renamed from packages/PrintSpooler/src/com/android/printspooler/renderer/PdfRendererService.java)140
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java232
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewController.java6
13 files changed, 701 insertions, 49 deletions
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 2106d38..dbaa4b8 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -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 \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 62d8036..a63258c 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -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),
diff --git a/core/jni/android/graphics/pdf/PdfEditor.cpp b/core/jni/android/graphics/pdf/PdfEditor.cpp
new file mode 100644
index 0000000..5f60c9e
--- /dev/null
+++ b/core/jni/android/graphics/pdf/PdfEditor.cpp
@@ -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));
+};
+
+};
diff --git a/graphics/java/android/graphics/pdf/PdfEditor.java b/graphics/java/android/graphics/pdf/PdfEditor.java
new file mode 100644
index 0000000..9837139
--- /dev/null
+++ b/graphics/java/android/graphics/pdf/PdfEditor.java
@@ -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);
+}
diff --git a/graphics/java/android/graphics/pdf/PdfRenderer.java b/graphics/java/android/graphics/pdf/PdfRenderer.java
index 1072b3c..359c294 100644
--- a/graphics/java/android/graphics/pdf/PdfRenderer.java
+++ b/graphics/java/android/graphics/pdf/PdfRenderer.java
@@ -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");
}
}
diff --git a/packages/PrintSpooler/Android.mk b/packages/PrintSpooler/Android.mk
index c4a55d1..27d1b23 100644
--- a/packages/PrintSpooler/Android.mk
+++ b/packages/PrintSpooler/Android.mk
@@ -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
diff --git a/packages/PrintSpooler/AndroidManifest.xml b/packages/PrintSpooler/AndroidManifest.xml
index 9efda2f..adff596 100644
--- a/packages/PrintSpooler/AndroidManifest.xml
+++ b/packages/PrintSpooler/AndroidManifest.xml
@@ -54,7 +54,7 @@
</service>
<service
- android:name=".renderer.PdfRendererService"
+ android:name=".renderer.PdfManipulationService"
android:isolatedProcess="true">
</service>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java b/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java
index a581e8a..1d8261b 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java
@@ -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() {
diff --git a/packages/PrintSpooler/src/com/android/printspooler/renderer/IPdfEditor.aidl b/packages/PrintSpooler/src/com/android/printspooler/renderer/IPdfEditor.aidl
new file mode 100644
index 0000000..b450ccb
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/renderer/IPdfEditor.aidl
@@ -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();
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/renderer/IPdfRenderer.aidl b/packages/PrintSpooler/src/com/android/printspooler/renderer/IPdfRenderer.aidl
index 1fba2b1..8e595d7 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/renderer/IPdfRenderer.aidl
+++ b/packages/PrintSpooler/src/com/android/printspooler/renderer/IPdfRenderer.aidl
@@ -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);
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/renderer/PdfRendererService.java b/packages/PrintSpooler/src/com/android/printspooler/renderer/PdfManipulationService.java
index 4d02c01..62716b2 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/renderer/PdfRendererService.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/renderer/PdfManipulationService.java
@@ -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);
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index 535081f..c517f2d 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -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;
+ }
+ }
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewController.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewController.java
index a25e05e..5d858ca 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewController.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewController.java
@@ -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);
+ }
}
});
}