Merge "Trim unnecessary pages when printing." into lmp-dev
This commit is contained in:
@@ -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 \
|
||||
|
||||
@@ -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),
|
||||
|
||||
161
core/jni/android/graphics/pdf/PdfEditor.cpp
Normal file
161
core/jni/android/graphics/pdf/PdfEditor.cpp
Normal 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));
|
||||
};
|
||||
|
||||
};
|
||||
162
graphics/java/android/graphics/pdf/PdfEditor.java
Normal file
162
graphics/java/android/graphics/pdf/PdfEditor.java
Normal 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);
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".renderer.PdfRendererService"
|
||||
android:name=".renderer.PdfManipulationService"
|
||||
android:isolatedProcess="true">
|
||||
</service>
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user