diff options
Diffstat (limited to 'packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java')
-rw-r--r-- | packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java | 734 |
1 files changed, 734 insertions, 0 deletions
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java new file mode 100644 index 0000000..2b27b69 --- /dev/null +++ b/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java @@ -0,0 +1,734 @@ +/* + * Copyright (C) 2013 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; + +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import android.content.ComponentName; +import android.content.Context; +import android.os.AsyncTask; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.print.IPrintClient; +import android.print.IPrintManager; +import android.print.PrintAttributes; +import android.print.PrintJobInfo; +import android.print.PrintManager; +import android.print.PrinterId; +import android.util.AtomicFile; +import android.util.Log; +import android.util.Slog; +import android.util.Xml; + +import com.android.internal.util.FastXmlSerializer; + +public class PrintSpooler { + + private static final String LOG_TAG = PrintSpooler.class.getSimpleName(); + + private static final boolean DEBUG_PRINT_JOB_LIFECYCLE = false; + + private static final boolean DEBUG_PERSISTENCE = false; + + private static final boolean PERSISTNECE_MANAGER_ENABLED = false; + + private static final String PRINT_FILE_EXTENSION = "pdf"; + + private static int sPrintJobIdCounter; + + private static final Object sLock = new Object(); + + private final Object mLock = new Object(); + + private static PrintSpooler sInstance; + + private final List<PrintJobInfo> mPrintJobs = new ArrayList<PrintJobInfo>(); + + private final PersistenceManager mPersistanceManager; + + private final Context mContext; + + private final IPrintManager mPrintManager; + + public static PrintSpooler getInstance(Context context) { + synchronized (sLock) { + if (sInstance == null) { + sInstance = new PrintSpooler(context); + } + return sInstance; + } + } + + private PrintSpooler(Context context) { + mContext = context; + mPersistanceManager = new PersistenceManager(); + mPersistanceManager.readStateLocked(); + mPrintManager = IPrintManager.Stub.asInterface( + ServiceManager.getService("print")); + } + + public List<PrintJobInfo> getPrintJobs(ComponentName componentName, int state, int appId) { + synchronized (mLock) { + List<PrintJobInfo> foundPrintJobs = null; + final int printJobCount = mPrintJobs.size(); + for (int i = 0; i < printJobCount; i++) { + PrintJobInfo printJob = mPrintJobs.get(i); + PrinterId printerId = printJob.getPrinterId(); + final boolean sameComponent = (componentName == null + || (printerId != null + && componentName.equals(printerId.getServiceComponentName()))); + final boolean sameAppId = appId == PrintManager.APP_ID_ANY + || printJob.getAppId() == appId; + final boolean sameState = state == PrintJobInfo.STATE_ANY + || state == printJob.getState(); + if (sameComponent && sameAppId && sameState) { + if (foundPrintJobs == null) { + foundPrintJobs = new ArrayList<PrintJobInfo>(); + } + foundPrintJobs.add(printJob); + } + } + return foundPrintJobs; + } + } + + public PrintJobInfo getPrintJob(int printJobId, int appId) { + synchronized (mLock) { + final int printJobCount = mPrintJobs.size(); + for (int i = 0; i < printJobCount; i++) { + PrintJobInfo printJob = mPrintJobs.get(i); + if (printJob.getId() == printJobId + && (appId == PrintManager.APP_ID_ANY || appId == printJob.getAppId())) { + return printJob; + } + } + return null; + } + } + + public boolean cancelPrintJob(int printJobId, int appId) { + synchronized (mLock) { + PrintJobInfo printJob = getPrintJob(printJobId, appId); + if (printJob != null) { + switch (printJob.getState()) { + case PrintJobInfo.STATE_CREATED: { + removePrintJobLocked(printJob); + } return true; + case PrintJobInfo.STATE_QUEUED: { + removePrintJobLocked(printJob); + } return true; + default: { + return false; + } + } + } + return false; + } + } + + public PrintJobInfo createPrintJob(CharSequence label, IPrintClient client, + PrintAttributes attributes, int appId) { + synchronized (mLock) { + final int printJobId = generatePrintJobIdLocked(); + PrintJobInfo printJob = new PrintJobInfo(); + printJob.setId(printJobId); + printJob.setAppId(appId); + printJob.setLabel(label); + printJob.setAttributes(attributes); + + addPrintJobLocked(printJob); + setPrintJobState(printJobId, PrintJobInfo.STATE_CREATED); + + return printJob; + } + } + + private int generatePrintJobIdLocked() { + int printJobId = sPrintJobIdCounter++; + while (isDuplicatePrintJobId(printJobId)) { + printJobId = sPrintJobIdCounter++; + } + return printJobId; + } + + private boolean isDuplicatePrintJobId(int printJobId) { + final int printJobCount = mPrintJobs.size(); + for (int j = 0; j < printJobCount; j++) { + PrintJobInfo printJob = mPrintJobs.get(j); + if (printJob.getId() == printJobId) { + return true; + } + } + return false; + } + + @SuppressWarnings("resource") + public boolean writePrintJobData(ParcelFileDescriptor fd, int printJobId) { + synchronized (mLock) { + FileInputStream in = null; + FileOutputStream out = null; + try { + PrintJobInfo printJob = getPrintJob(printJobId, PrintManager.APP_ID_ANY); + if (printJob != null) { + File file = generateFileForPrintJob(printJobId); + in = new FileInputStream(file); + out = new FileOutputStream(fd.getFileDescriptor()); + final byte[] buffer = new byte[8192]; + while (true) { + final int readByteCount = in.read(buffer); + if (readByteCount < 0) { + return true; + } + out.write(buffer, 0, readByteCount); + } + } + } catch (FileNotFoundException fnfe) { + Log.e(LOG_TAG, "Error writing print job data!", fnfe); + } catch (IOException ioe) { + Log.e(LOG_TAG, "Error writing print job data!", ioe); + } finally { + closeIfNotNullNoException(in); + closeIfNotNullNoException(out); + closeIfNotNullNoException(fd); + } + } + return false; + } + + private void closeIfNotNullNoException(Closeable closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (IOException ioe) { + /* ignore */; + } + } + } + + public File generateFileForPrintJob(int printJobId) { + return new File(mContext.getFilesDir(), "print_job_" + + printJobId + "." + PRINT_FILE_EXTENSION); + } + + private void addPrintJobLocked(PrintJobInfo printJob) { + mPrintJobs.add(printJob); + if (DEBUG_PRINT_JOB_LIFECYCLE) { + Slog.i(LOG_TAG, "[ADD] " + printJob); + } + } + + private void removePrintJobLocked(PrintJobInfo printJob) { + if (mPrintJobs.remove(printJob)) { + generateFileForPrintJob(printJob.getId()).delete(); + if (DEBUG_PRINT_JOB_LIFECYCLE) { + Slog.i(LOG_TAG, "[REMOVE] " + printJob); + } + } + } + + public boolean setPrintJobState(int printJobId, int state) { + boolean success = false; + PrintJobInfo queuedPrintJob = null; + + synchronized (mLock) { + PrintJobInfo printJob = getPrintJob(printJobId, PrintManager.APP_ID_ANY); + if (printJob != null && printJob.getState() < state) { + success = true; + printJob.setState(state); + // TODO: Update notifications. + switch (state) { + case PrintJobInfo.STATE_COMPLETED: + case PrintJobInfo.STATE_CANCELED: { + removePrintJobLocked(printJob); + } break; + case PrintJobInfo.STATE_QUEUED: { + queuedPrintJob = new PrintJobInfo(printJob); + } break; + } + if (DEBUG_PRINT_JOB_LIFECYCLE) { + Slog.i(LOG_TAG, "[STATUS CHANGED] " + printJob); + } + mPersistanceManager.writeStateLocked(); + } + } + + if (queuedPrintJob != null) { + try { + mPrintManager.onPrintJobQueued(queuedPrintJob.getPrinterId(), + queuedPrintJob); + } catch (RemoteException re) { + /* ignore */ + } + } + + return success; + } + + public boolean setPrintJobTag(int printJobId, String tag) { + synchronized (mLock) { + PrintJobInfo printJob = getPrintJob(printJobId, PrintManager.APP_ID_ANY); + if (printJob != null) { + printJob.setTag(tag); + mPersistanceManager.writeStateLocked(); + return true; + } + } + return false; + } + + public void setPrintJobAttributes(int printJobId, PrintAttributes attributes) { + synchronized (mLock) { + PrintJobInfo printJob = getPrintJob(printJobId, PrintManager.APP_ID_ANY); + if (printJob != null) { + printJob.setAttributes(attributes); + mPersistanceManager.writeStateLocked(); + } + } + } + + public void setPrintJobPrinterId(int printJobId, PrinterId printerId) { + synchronized (mLock) { + PrintJobInfo printJob = getPrintJob(printJobId, PrintManager.APP_ID_ANY); + if (printJob != null) { + printJob.setPrinterId(printerId); + mPersistanceManager.writeStateLocked(); + } + } + } + + private final class PersistenceManager { + private static final String PERSIST_FILE_NAME = "print_spooler_state.xml"; + + private static final String TAG_SPOOLER = "spooler"; + private static final String TAG_JOB = "job"; + private static final String TAG_ID = "id"; + private static final String TAG_TAG = "tag"; + private static final String TAG_APP_ID = "app-id"; + private static final String TAG_STATE = "state"; + private static final String TAG_ATTRIBUTES = "attributes"; + private static final String TAG_LABEL = "label"; + private static final String TAG_PRINTER = "printer"; + + private static final String ATTRIBUTE_MEDIA_SIZE = "mediaSize"; + private static final String ATTRIBUTE_RESOLUTION = "resolution"; + private static final String ATTRIBUTE_MARGINS = "margins"; + private static final String ATTRIBUTE_INPUT_TRAY = "inputTray"; + private static final String ATTRIBUTE_OUTPUT_TRAY = "outputTray"; + private static final String ATTRIBUTE_DUPLEX_MODE = "duplexMode"; + private static final String ATTRIBUTE_COLOR_MODE = "colorMode"; + private static final String ATTRIBUTE_FITTING_MODE = "fittingMode"; + private static final String ATTRIBUTE_ORIENTATION = "orientation"; + + private final AtomicFile mStatePersistFile; + + private boolean mWriteStateScheduled; + + private PersistenceManager() { + mStatePersistFile = new AtomicFile(new File(mContext.getFilesDir(), + PERSIST_FILE_NAME)); + } + + public void writeStateLocked() { + // TODO: Implement persistence of PrintableInfo + if (!PERSISTNECE_MANAGER_ENABLED) { + return; + } + if (mWriteStateScheduled) { + return; + } + mWriteStateScheduled = true; + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... params) { + synchronized (mLock) { + mWriteStateScheduled = false; + doWriteStateLocked(); + } + return null; + } + }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null); + } + + private void doWriteStateLocked() { + FileOutputStream out = null; + try { + out = mStatePersistFile.startWrite(); + + XmlSerializer serializer = new FastXmlSerializer(); + serializer.setOutput(out, "utf-8"); + serializer.startDocument(null, true); + serializer.startTag(null, TAG_SPOOLER); + + List<PrintJobInfo> printJobs = mPrintJobs; + + final int printJobCount = printJobs.size(); + for (int j = 0; j < printJobCount; j++) { + PrintJobInfo printJob = printJobs.get(j); + + final int state = printJob.getState(); + if (state < PrintJobInfo.STATE_QUEUED + || state > PrintJobInfo.STATE_FAILED) { + continue; + } + + serializer.startTag(null, TAG_JOB); + + serializer.startTag(null, TAG_ID); + serializer.text(String.valueOf(printJob.getId())); + serializer.endTag(null, TAG_ID); + + serializer.startTag(null, TAG_TAG); + serializer.text(printJob.getTag()); + serializer.endTag(null, TAG_TAG); + + serializer.startTag(null, TAG_APP_ID); + serializer.text(String.valueOf(printJob.getAppId())); + serializer.endTag(null, TAG_APP_ID); + + serializer.startTag(null, TAG_LABEL); + serializer.text(printJob.getLabel().toString()); + serializer.endTag(null, TAG_LABEL); + + serializer.startTag(null, TAG_STATE); + serializer.text(String.valueOf(printJob.getState())); + serializer.endTag(null, TAG_STATE); + + serializer.startTag(null, TAG_PRINTER); + serializer.text(printJob.getPrinterId().flattenToString()); + serializer.endTag(null, TAG_PRINTER); + + PrintAttributes attributes = printJob.getAttributes(); + if (attributes != null) { + serializer.startTag(null, TAG_ATTRIBUTES); + + //TODO: Implement persistence of the attributes below. + +// MediaSize mediaSize = attributes.getMediaSize(); +// if (mediaSize != null) { +// serializer.attribute(null, ATTRIBUTE_MEDIA_SIZE, +// mediaSize.flattenToString()); +// } +// +// Resolution resolution = attributes.getResolution(); +// if (resolution != null) { +// serializer.attribute(null, ATTRIBUTE_RESOLUTION, +// resolution.flattenToString()); +// } +// +// Margins margins = attributes.getMargins(); +// if (margins != null) { +// serializer.attribute(null, ATTRIBUTE_MARGINS, +// margins.flattenToString()); +// } +// +// Tray inputTray = attributes.getInputTray(); +// if (inputTray != null) { +// serializer.attribute(null, ATTRIBUTE_INPUT_TRAY, +// inputTray.flattenToString()); +// } +// +// Tray outputTray = attributes.getOutputTray(); +// if (outputTray != null) { +// serializer.attribute(null, ATTRIBUTE_OUTPUT_TRAY, +// outputTray.flattenToString()); +// } + + final int duplexMode = attributes.getDuplexMode(); + if (duplexMode > 0) { + serializer.attribute(null, ATTRIBUTE_DUPLEX_MODE, + String.valueOf(duplexMode)); + } + + final int colorMode = attributes.getColorMode(); + if (colorMode > 0) { + serializer.attribute(null, ATTRIBUTE_COLOR_MODE, + String.valueOf(colorMode)); + } + + final int fittingMode = attributes.getFittingMode(); + if (fittingMode > 0) { + serializer.attribute(null, ATTRIBUTE_FITTING_MODE, + String.valueOf(fittingMode)); + } + + final int orientation = attributes.getOrientation(); + if (orientation > 0) { + serializer.attribute(null, ATTRIBUTE_ORIENTATION, + String.valueOf(orientation)); + } + + serializer.endTag(null, TAG_ATTRIBUTES); + } + + serializer.endTag(null, TAG_JOB); + + if (DEBUG_PERSISTENCE) { + Log.i(LOG_TAG, "[PERSISTED] " + printJob); + } + } + + serializer.endTag(null, TAG_SPOOLER); + serializer.endDocument(); + mStatePersistFile.finishWrite(out); + } catch (IOException e) { + Slog.w(LOG_TAG, "Failed to write state, restoring backup.", e); + mStatePersistFile.failWrite(out); + } finally { + if (out != null) { + try { + out.close(); + } catch (IOException ioe) { + /* ignore */ + } + } + } + } + + public void readStateLocked() { + if (!PERSISTNECE_MANAGER_ENABLED) { + return; + } + FileInputStream in = null; + try { + in = mStatePersistFile.openRead(); + } catch (FileNotFoundException e) { + Log.i(LOG_TAG, "No existing print spooler state."); + return; + } + try { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(in, null); + parseState(parser); + } catch (IllegalStateException ise) { + Slog.w(LOG_TAG, "Failed parsing " + ise); + } catch (NullPointerException npe) { + Slog.w(LOG_TAG, "Failed parsing " + npe); + } catch (NumberFormatException nfe) { + Slog.w(LOG_TAG, "Failed parsing " + nfe); + } catch (XmlPullParserException xppe) { + Slog.w(LOG_TAG, "Failed parsing " + xppe); + } catch (IOException ioe) { + Slog.w(LOG_TAG, "Failed parsing " + ioe); + } catch (IndexOutOfBoundsException iobe) { + Slog.w(LOG_TAG, "Failed parsing " + iobe); + } finally { + try { + in.close(); + } catch (IOException ioe) { + /* ignore */ + } + } + } + + private void parseState(XmlPullParser parser) + throws IOException, XmlPullParserException { + parser.next(); + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.START_TAG, TAG_SPOOLER); + parser.next(); + + while (parsePrintJob(parser)) { + parser.next(); + } + + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_SPOOLER); + } + + private boolean parsePrintJob(XmlPullParser parser) + throws IOException, XmlPullParserException { + skipEmptyTextTags(parser); + if (!accept(parser, XmlPullParser.START_TAG, TAG_JOB)) { + return false; + } + parser.next(); + + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.START_TAG, TAG_ID); + parser.next(); + final int printJobId = Integer.parseInt(parser.getText()); + parser.next(); + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_ID); + parser.next(); + + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.START_TAG, TAG_TAG); + parser.next(); + String tag = parser.getText(); + parser.next(); + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_TAG); + parser.next(); + + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.START_TAG, TAG_APP_ID); + parser.next(); + final int appId = Integer.parseInt(parser.getText()); + parser.next(); + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_APP_ID); + parser.next(); + + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.START_TAG, TAG_LABEL); + parser.next(); + String label = parser.getText(); + parser.next(); + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_LABEL); + parser.next(); + + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.START_TAG, TAG_STATE); + parser.next(); + final int state = Integer.parseInt(parser.getText()); + parser.next(); + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_STATE); + parser.next(); + + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.START_TAG, TAG_PRINTER); + parser.next(); + PrinterId printerId = PrinterId.unflattenFromString(parser.getText()); + parser.next(); + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_PRINTER); + parser.next(); + + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.START_TAG, TAG_ATTRIBUTES); + + final int attributeCount = parser.getAttributeCount(); + PrintAttributes attributes = null; + if (attributeCount > 0) { + PrintAttributes.Builder builder = new PrintAttributes.Builder(); + + // TODO: Implement reading of the attributes below. + +// String mediaSize = parser.getAttributeValue(null, ATTRIBUTE_MEDIA_SIZE); +// if (mediaSize != null) { +// builder.setMediaSize(MediaSize.unflattenFromString(mediaSize)); +// } +// +// String resolution = parser.getAttributeValue(null, ATTRIBUTE_RESOLUTION); +// if (resolution != null) { +// builder.setMediaSize(Resolution.unflattenFromString(resolution)); +// } +// +// String margins = parser.getAttributeValue(null, ATTRIBUTE_MARGINS); +// if (margins != null) { +// builder.setMediaSize(Margins.unflattenFromString(margins)); +// } +// +// String inputTray = parser.getAttributeValue(null, ATTRIBUTE_INPUT_TRAY); +// if (inputTray != null) { +// builder.setMediaSize(Tray.unflattenFromString(inputTray)); +// } +// +// String outputTray = parser.getAttributeValue(null, ATTRIBUTE_OUTPUT_TRAY); +// if (outputTray != null) { +// builder.setMediaSize(Tray.unflattenFromString(outputTray)); +// } +// +// String duplexMode = parser.getAttributeValue(null, ATTRIBUTE_DUPLEX_MODE); +// if (duplexMode != null) { +// builder.setDuplexMode(Integer.parseInt(duplexMode)); +// } + + String colorMode = parser.getAttributeValue(null, ATTRIBUTE_COLOR_MODE); + if (colorMode != null) { + builder.setColorMode(Integer.parseInt(colorMode)); + } + + String fittingMode = parser.getAttributeValue(null, ATTRIBUTE_COLOR_MODE); + if (fittingMode != null) { + builder.setFittingMode(Integer.parseInt(fittingMode)); + } + } + parser.next(); + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_ATTRIBUTES); + parser.next(); + + PrintJobInfo printJob = new PrintJobInfo(); + printJob.setId(printJobId); + printJob.setTag(tag); + printJob.setAppId(appId); + printJob.setLabel(label); + printJob.setState(state); + printJob.setAttributes(attributes); + printJob.setPrinterId(printerId); + + mPrintJobs.add(printJob); + + if (DEBUG_PERSISTENCE) { + Log.i(LOG_TAG, "[RESTORED] " + printJob); + } + + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_JOB); + + return true; + } + + private void expect(XmlPullParser parser, int type, String tag) + throws IOException, XmlPullParserException { + if (!accept(parser, type, tag)) { + throw new XmlPullParserException("Exepected event: " + type + + " and tag: " + tag + " but got event: " + parser.getEventType() + + " and tag:" + parser.getName()); + } + } + + private void skipEmptyTextTags(XmlPullParser parser) + throws IOException, XmlPullParserException { + while (accept(parser, XmlPullParser.TEXT, null) + && "\n".equals(parser.getText())) { + parser.next(); + } + } + + private boolean accept(XmlPullParser parser, int type, String tag) + throws IOException, XmlPullParserException { + if (parser.getEventType() != type) { + return false; + } + if (tag != null) { + if (!tag.equals(parser.getName())) { + return false; + } + } else if (parser.getName() != null) { + return false; + } + return true; + } + } +} |