2 * Copyright (C) 2013 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.printspooler;
19 import android.app.Service;
20 import android.content.ComponentName;
21 import android.content.Intent;
22 import android.os.AsyncTask;
23 import android.os.IBinder;
24 import android.os.Message;
25 import android.os.ParcelFileDescriptor;
26 import android.os.RemoteException;
27 import android.print.IPrintSpooler;
28 import android.print.IPrintSpoolerCallbacks;
29 import android.print.IPrintSpoolerClient;
30 import android.print.PageRange;
31 import android.print.PrintAttributes;
32 import android.print.PrintAttributes.Margins;
33 import android.print.PrintAttributes.MediaSize;
34 import android.print.PrintAttributes.Resolution;
35 import android.print.PrintDocumentInfo;
36 import android.print.PrintJobId;
37 import android.print.PrintJobInfo;
38 import android.print.PrintManager;
39 import android.print.PrinterId;
40 import android.print.PrinterInfo;
41 import android.text.TextUtils;
42 import android.util.ArrayMap;
43 import android.util.AtomicFile;
44 import android.util.Log;
45 import android.util.Slog;
46 import android.util.Xml;
48 import com.android.internal.os.HandlerCaller;
49 import com.android.internal.util.FastXmlSerializer;
51 import libcore.io.IoUtils;
53 import org.xmlpull.v1.XmlPullParser;
54 import org.xmlpull.v1.XmlPullParserException;
55 import org.xmlpull.v1.XmlSerializer;
58 import java.io.FileDescriptor;
59 import java.io.FileInputStream;
60 import java.io.FileNotFoundException;
61 import java.io.FileOutputStream;
62 import java.io.IOException;
63 import java.io.PrintWriter;
64 import java.util.ArrayList;
65 import java.util.List;
68 * Service for exposing some of the {@link PrintSpooler} functionality to
71 public final class PrintSpoolerService extends Service {
73 private static final String LOG_TAG = "PrintSpoolerService";
75 private static final boolean DEBUG_PRINT_JOB_LIFECYCLE = false;
77 private static final boolean DEBUG_PERSISTENCE = false;
79 private static final boolean PERSISTNECE_MANAGER_ENABLED = true;
81 private static final long CHECK_ALL_PRINTJOBS_HANDLED_DELAY = 5000;
83 private static final String PRINT_JOB_FILE_PREFIX = "print_job_";
85 private static final String PRINT_FILE_EXTENSION = "pdf";
87 private static final Object sLock = new Object();
89 private final Object mLock = new Object();
91 private final List<PrintJobInfo> mPrintJobs = new ArrayList<PrintJobInfo>();
93 private static PrintSpoolerService sInstance;
95 private IPrintSpoolerClient mClient;
97 private HandlerCaller mHandlerCaller;
99 private PersistenceManager mPersistanceManager;
101 private NotificationController mNotificationController;
103 public static PrintSpoolerService peekInstance() {
104 synchronized (sLock) {
110 public void onCreate() {
112 mHandlerCaller = new HandlerCaller(this, getMainLooper(),
113 new HandlerCallerCallback(), false);
115 mPersistanceManager = new PersistenceManager();
116 mNotificationController = new NotificationController(PrintSpoolerService.this);
118 synchronized (mLock) {
119 mPersistanceManager.readStateLocked();
120 handleReadPrintJobsLocked();
123 synchronized (sLock) {
129 public IBinder onBind(Intent intent) {
130 return new PrintSpooler();
134 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
135 synchronized (mLock) {
136 String prefix = (args.length > 0) ? args[0] : "";
139 pw.append(prefix).append("print jobs:").println();
140 final int printJobCount = mPrintJobs.size();
141 for (int i = 0; i < printJobCount; i++) {
142 PrintJobInfo printJob = mPrintJobs.get(i);
143 pw.append(prefix).append(tab).append(printJob.toString());
147 pw.append(prefix).append("print job files:").println();
148 File[] files = getFilesDir().listFiles();
150 final int fileCount = files.length;
151 for (int i = 0; i < fileCount; i++) {
152 File file = files[i];
153 if (file.isFile() && file.getName().startsWith(PRINT_JOB_FILE_PREFIX)) {
154 pw.append(prefix).append(tab).append(file.getName()).println();
161 private void sendOnPrintJobQueued(PrintJobInfo printJob) {
162 Message message = mHandlerCaller.obtainMessageO(
163 HandlerCallerCallback.MSG_ON_PRINT_JOB_QUEUED, printJob);
164 mHandlerCaller.executeOrSendMessage(message);
167 private void sendOnAllPrintJobsForServiceHandled(ComponentName service) {
168 Message message = mHandlerCaller.obtainMessageO(
169 HandlerCallerCallback.MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED, service);
170 mHandlerCaller.executeOrSendMessage(message);
173 private void sendOnAllPrintJobsHandled() {
174 Message message = mHandlerCaller.obtainMessage(
175 HandlerCallerCallback.MSG_ON_ALL_PRINT_JOBS_HANDLED);
176 mHandlerCaller.executeOrSendMessage(message);
179 private final class HandlerCallerCallback implements HandlerCaller.Callback {
180 public static final int MSG_SET_CLIENT = 1;
181 public static final int MSG_ON_PRINT_JOB_QUEUED = 2;
182 public static final int MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED = 3;
183 public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 4;
184 public static final int MSG_CHECK_ALL_PRINTJOBS_HANDLED = 5;
185 public static final int MSG_ON_PRINT_JOB_STATE_CHANGED = 6;
188 public void executeMessage(Message message) {
189 switch (message.what) {
190 case MSG_SET_CLIENT: {
191 synchronized (mLock) {
192 mClient = (IPrintSpoolerClient) message.obj;
193 if (mClient != null) {
194 Message msg = mHandlerCaller.obtainMessage(
195 HandlerCallerCallback.MSG_CHECK_ALL_PRINTJOBS_HANDLED);
196 mHandlerCaller.sendMessageDelayed(msg,
197 CHECK_ALL_PRINTJOBS_HANDLED_DELAY);
202 case MSG_ON_PRINT_JOB_QUEUED: {
203 PrintJobInfo printJob = (PrintJobInfo) message.obj;
204 if (mClient != null) {
206 mClient.onPrintJobQueued(printJob);
207 } catch (RemoteException re) {
208 Slog.e(LOG_TAG, "Error notify for a queued print job.", re);
213 case MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED: {
214 ComponentName service = (ComponentName) message.obj;
215 if (mClient != null) {
217 mClient.onAllPrintJobsForServiceHandled(service);
218 } catch (RemoteException re) {
219 Slog.e(LOG_TAG, "Error notify for all print jobs per service"
225 case MSG_ON_ALL_PRINT_JOBS_HANDLED: {
226 if (mClient != null) {
228 mClient.onAllPrintJobsHandled();
229 } catch (RemoteException re) {
230 Slog.e(LOG_TAG, "Error notify for all print job handled.", re);
235 case MSG_CHECK_ALL_PRINTJOBS_HANDLED: {
236 checkAllPrintJobsHandled();
239 case MSG_ON_PRINT_JOB_STATE_CHANGED: {
240 if (mClient != null) {
241 PrintJobInfo printJob = (PrintJobInfo) message.obj;
243 mClient.onPrintJobStateChanged(printJob);
244 } catch (RemoteException re) {
245 Slog.e(LOG_TAG, "Error notify for print job state change.", re);
253 public List<PrintJobInfo> getPrintJobInfos(ComponentName componentName,
254 int state, int appId) {
255 List<PrintJobInfo> foundPrintJobs = null;
256 synchronized (mLock) {
257 final int printJobCount = mPrintJobs.size();
258 for (int i = 0; i < printJobCount; i++) {
259 PrintJobInfo printJob = mPrintJobs.get(i);
260 PrinterId printerId = printJob.getPrinterId();
261 final boolean sameComponent = (componentName == null
262 || (printerId != null
263 && componentName.equals(printerId.getServiceName())));
264 final boolean sameAppId = appId == PrintManager.APP_ID_ANY
265 || printJob.getAppId() == appId;
266 final boolean sameState = (state == printJob.getState())
267 || (state == PrintJobInfo.STATE_ANY)
268 || (state == PrintJobInfo.STATE_ANY_VISIBLE_TO_CLIENTS
269 && isStateVisibleToUser(printJob.getState()))
270 || (state == PrintJobInfo.STATE_ANY_ACTIVE
271 && isActiveState(printJob.getState()))
272 || (state == PrintJobInfo.STATE_ANY_SCHEDULED
273 && isScheduledState(printJob.getState()));
274 if (sameComponent && sameAppId && sameState) {
275 if (foundPrintJobs == null) {
276 foundPrintJobs = new ArrayList<PrintJobInfo>();
278 foundPrintJobs.add(printJob);
282 return foundPrintJobs;
285 private boolean isStateVisibleToUser(int state) {
286 return (isActiveState(state) && (state == PrintJobInfo.STATE_FAILED
287 || state == PrintJobInfo.STATE_COMPLETED || state == PrintJobInfo.STATE_CANCELED
288 || state == PrintJobInfo.STATE_BLOCKED));
291 public PrintJobInfo getPrintJobInfo(PrintJobId printJobId, int appId) {
292 synchronized (mLock) {
293 final int printJobCount = mPrintJobs.size();
294 for (int i = 0; i < printJobCount; i++) {
295 PrintJobInfo printJob = mPrintJobs.get(i);
296 if (printJob.getId().equals(printJobId)
297 && (appId == PrintManager.APP_ID_ANY
298 || appId == printJob.getAppId())) {
306 public void createPrintJob(PrintJobInfo printJob) {
307 synchronized (mLock) {
308 addPrintJobLocked(printJob);
309 setPrintJobState(printJob.getId(), PrintJobInfo.STATE_CREATED, null);
311 Message message = mHandlerCaller.obtainMessageO(
312 HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED,
314 mHandlerCaller.executeOrSendMessage(message);
318 private void handleReadPrintJobsLocked() {
319 // Make a map with the files for a print job since we may have
320 // to delete some. One example of getting orphan files if the
321 // spooler crashes while constructing a print job. We do not
322 // persist partially populated print jobs under construction to
323 // avoid special handling for various attributes missing.
324 ArrayMap<PrintJobId, File> fileForJobMap = null;
325 File[] files = getFilesDir().listFiles();
327 final int fileCount = files.length;
328 for (int i = 0; i < fileCount; i++) {
329 File file = files[i];
330 if (file.isFile() && file.getName().startsWith(PRINT_JOB_FILE_PREFIX)) {
331 if (fileForJobMap == null) {
332 fileForJobMap = new ArrayMap<PrintJobId, File>();
334 String printJobIdString = file.getName().substring(
335 PRINT_JOB_FILE_PREFIX.length(),
336 file.getName().indexOf('.'));
337 PrintJobId printJobId = PrintJobId.unflattenFromString(
339 fileForJobMap.put(printJobId, file);
344 final int printJobCount = mPrintJobs.size();
345 for (int i = 0; i < printJobCount; i++) {
346 PrintJobInfo printJob = mPrintJobs.get(i);
348 // We want to have only the orphan files at the end.
349 if (fileForJobMap != null) {
350 fileForJobMap.remove(printJob.getId());
353 switch (printJob.getState()) {
354 case PrintJobInfo.STATE_QUEUED:
355 case PrintJobInfo.STATE_STARTED:
356 case PrintJobInfo.STATE_BLOCKED: {
357 // We have a print job that was queued or started or blocked in
358 // the past but the device battery died or a crash occurred. In
359 // this case we assume the print job failed and let the user
360 // decide whether to restart the job or just cancel it.
361 setPrintJobState(printJob.getId(), PrintJobInfo.STATE_FAILED,
362 getString(R.string.no_connection_to_printer));
367 if (!mPrintJobs.isEmpty()) {
368 // Update the notification.
369 mNotificationController.onUpdateNotifications(mPrintJobs);
372 // Delete the orphan files.
373 if (fileForJobMap != null) {
374 final int orphanFileCount = fileForJobMap.size();
375 for (int i = 0; i < orphanFileCount; i++) {
376 File file = fileForJobMap.valueAt(i);
382 public void checkAllPrintJobsHandled() {
383 synchronized (mLock) {
384 if (!hasActivePrintJobsLocked()) {
385 notifyOnAllPrintJobsHandled();
390 public void writePrintJobData(final ParcelFileDescriptor fd, final PrintJobId printJobId) {
391 final PrintJobInfo printJob;
392 synchronized (mLock) {
393 printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
395 new AsyncTask<Void, Void, Void>() {
397 protected Void doInBackground(Void... params) {
398 FileInputStream in = null;
399 FileOutputStream out = null;
401 if (printJob != null) {
402 File file = generateFileForPrintJob(printJobId);
403 in = new FileInputStream(file);
404 out = new FileOutputStream(fd.getFileDescriptor());
406 final byte[] buffer = new byte[8192];
408 final int readByteCount = in.read(buffer);
409 if (readByteCount < 0) {
412 out.write(buffer, 0, readByteCount);
414 } catch (FileNotFoundException fnfe) {
415 Log.e(LOG_TAG, "Error writing print job data!", fnfe);
416 } catch (IOException ioe) {
417 Log.e(LOG_TAG, "Error writing print job data!", ioe);
419 IoUtils.closeQuietly(in);
420 IoUtils.closeQuietly(out);
421 IoUtils.closeQuietly(fd);
423 Log.i(LOG_TAG, "[END WRITE]");
426 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
429 public File generateFileForPrintJob(PrintJobId printJobId) {
430 return new File(getFilesDir(), PRINT_JOB_FILE_PREFIX
431 + printJobId.flattenToString() + "." + PRINT_FILE_EXTENSION);
434 private void addPrintJobLocked(PrintJobInfo printJob) {
435 mPrintJobs.add(printJob);
436 if (DEBUG_PRINT_JOB_LIFECYCLE) {
437 Slog.i(LOG_TAG, "[ADD] " + printJob);
441 private void removeObsoletePrintJobs() {
442 synchronized (mLock) {
443 final int printJobCount = mPrintJobs.size();
444 for (int i = printJobCount - 1; i >= 0; i--) {
445 PrintJobInfo printJob = mPrintJobs.get(i);
446 if (isObsoleteState(printJob.getState())) {
447 mPrintJobs.remove(i);
448 if (DEBUG_PRINT_JOB_LIFECYCLE) {
449 Slog.i(LOG_TAG, "[REMOVE] " + printJob.getId().flattenToString());
451 removePrintJobFileLocked(printJob.getId());
454 mPersistanceManager.writeStateLocked();
458 private void removePrintJobFileLocked(PrintJobId printJobId) {
459 File file = generateFileForPrintJob(printJobId);
462 if (DEBUG_PRINT_JOB_LIFECYCLE) {
463 Slog.i(LOG_TAG, "[REMOVE FILE FOR] " + printJobId);
468 public boolean setPrintJobState(PrintJobId printJobId, int state, String error) {
469 boolean success = false;
471 synchronized (mLock) {
472 PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
473 if (printJob != null) {
474 final int oldState = printJob.getState();
475 if (oldState == state) {
481 printJob.setState(state);
482 printJob.setStateReason(error);
483 printJob.setCancelling(false);
485 if (DEBUG_PRINT_JOB_LIFECYCLE) {
486 Slog.i(LOG_TAG, "[STATE CHANGED] " + printJob);
490 case PrintJobInfo.STATE_COMPLETED:
491 case PrintJobInfo.STATE_CANCELED:
492 mPrintJobs.remove(printJob);
493 removePrintJobFileLocked(printJob.getId());
496 case PrintJobInfo.STATE_FAILED: {
497 PrinterId printerId = printJob.getPrinterId();
498 if (printerId != null) {
499 ComponentName service = printerId.getServiceName();
500 if (!hasActivePrintJobsForServiceLocked(service)) {
501 sendOnAllPrintJobsForServiceHandled(service);
506 case PrintJobInfo.STATE_QUEUED: {
507 sendOnPrintJobQueued(new PrintJobInfo(printJob));
511 if (shouldPersistPrintJob(printJob)) {
512 mPersistanceManager.writeStateLocked();
515 if (!hasActivePrintJobsLocked()) {
516 notifyOnAllPrintJobsHandled();
519 Message message = mHandlerCaller.obtainMessageO(
520 HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED,
522 mHandlerCaller.executeOrSendMessage(message);
524 mNotificationController.onUpdateNotifications(mPrintJobs);
531 public boolean hasActivePrintJobsLocked() {
532 final int printJobCount = mPrintJobs.size();
533 for (int i = 0; i < printJobCount; i++) {
534 PrintJobInfo printJob = mPrintJobs.get(i);
535 if (isActiveState(printJob.getState())) {
542 public boolean hasActivePrintJobsForServiceLocked(ComponentName service) {
543 final int printJobCount = mPrintJobs.size();
544 for (int i = 0; i < printJobCount; i++) {
545 PrintJobInfo printJob = mPrintJobs.get(i);
546 if (isActiveState(printJob.getState())
547 && printJob.getPrinterId().getServiceName().equals(service)) {
554 private boolean isObsoleteState(int printJobState) {
555 return (isTeminalState(printJobState)
556 || printJobState == PrintJobInfo.STATE_QUEUED);
559 private boolean isScheduledState(int printJobState) {
560 return printJobState == PrintJobInfo.STATE_QUEUED
561 || printJobState == PrintJobInfo.STATE_STARTED
562 || printJobState == PrintJobInfo.STATE_BLOCKED;
565 private boolean isActiveState(int printJobState) {
566 return printJobState == PrintJobInfo.STATE_CREATED
567 || printJobState == PrintJobInfo.STATE_QUEUED
568 || printJobState == PrintJobInfo.STATE_STARTED
569 || printJobState == PrintJobInfo.STATE_BLOCKED;
572 private boolean isTeminalState(int printJobState) {
573 return printJobState == PrintJobInfo.STATE_COMPLETED
574 || printJobState == PrintJobInfo.STATE_CANCELED;
577 public boolean setPrintJobTag(PrintJobId printJobId, String tag) {
578 synchronized (mLock) {
579 PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
580 if (printJob != null) {
581 String printJobTag = printJob.getTag();
582 if (printJobTag == null) {
586 } else if (printJobTag.equals(tag)) {
589 printJob.setTag(tag);
590 if (shouldPersistPrintJob(printJob)) {
591 mPersistanceManager.writeStateLocked();
599 public void setPrintJobCancelling(PrintJobId printJobId, boolean cancelling) {
600 synchronized (mLock) {
601 PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
602 if (printJob != null) {
603 printJob.setCancelling(cancelling);
604 if (shouldPersistPrintJob(printJob)) {
605 mPersistanceManager.writeStateLocked();
607 mNotificationController.onUpdateNotifications(mPrintJobs);
609 Message message = mHandlerCaller.obtainMessageO(
610 HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED,
612 mHandlerCaller.executeOrSendMessage(message);
617 public void setPrintJobCopiesNoPersistence(PrintJobId printJobId, int copies) {
618 synchronized (mLock) {
619 PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
620 if (printJob != null) {
621 printJob.setCopies(copies);
626 public void setPrintJobPrintDocumentInfoNoPersistence(PrintJobId printJobId,
627 PrintDocumentInfo info) {
628 synchronized (mLock) {
629 PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
630 if (printJob != null) {
631 printJob.setDocumentInfo(info);
636 public void setPrintJobAttributesNoPersistence(PrintJobId printJobId,
637 PrintAttributes attributes) {
638 synchronized (mLock) {
639 PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
640 if (printJob != null) {
641 printJob.setAttributes(attributes);
646 public void setPrintJobPrinterNoPersistence(PrintJobId printJobId, PrinterInfo printer) {
647 synchronized (mLock) {
648 PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
649 if (printJob != null) {
650 printJob.setPrinterId(printer.getId());
651 printJob.setPrinterName(printer.getName());
656 public void setPrintJobPagesNoPersistence(PrintJobId printJobId, PageRange[] pages) {
657 synchronized (mLock) {
658 PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
659 if (printJob != null) {
660 printJob.setPages(pages);
665 private boolean shouldPersistPrintJob(PrintJobInfo printJob) {
666 return printJob.getState() >= PrintJobInfo.STATE_QUEUED;
669 private void notifyOnAllPrintJobsHandled() {
670 // This has to run on the tread that is persisting the current state
671 // since this call may result in the system unbinding from the spooler
672 // and as a result the spooler process may get killed before the write
674 new AsyncTask<Void, Void, Void>() {
676 protected Void doInBackground(Void... params) {
677 sendOnAllPrintJobsHandled();
680 }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
683 private final class PersistenceManager {
684 private static final String PERSIST_FILE_NAME = "print_spooler_state.xml";
686 private static final String TAG_SPOOLER = "spooler";
687 private static final String TAG_JOB = "job";
689 private static final String TAG_PRINTER_ID = "printerId";
690 private static final String TAG_PAGE_RANGE = "pageRange";
691 private static final String TAG_ATTRIBUTES = "attributes";
692 private static final String TAG_DOCUMENT_INFO = "documentInfo";
694 private static final String ATTR_ID = "id";
695 private static final String ATTR_LABEL = "label";
696 private static final String ATTR_LABEL_RES_ID = "labelResId";
697 private static final String ATTR_PACKAGE_NAME = "packageName";
698 private static final String ATTR_STATE = "state";
699 private static final String ATTR_APP_ID = "appId";
700 private static final String ATTR_TAG = "tag";
701 private static final String ATTR_CREATION_TIME = "creationTime";
702 private static final String ATTR_COPIES = "copies";
703 private static final String ATTR_PRINTER_NAME = "printerName";
704 private static final String ATTR_STATE_REASON = "stateReason";
705 private static final String ATTR_CANCELLING = "cancelling";
707 private static final String TAG_MEDIA_SIZE = "mediaSize";
708 private static final String TAG_RESOLUTION = "resolution";
709 private static final String TAG_MARGINS = "margins";
711 private static final String ATTR_COLOR_MODE = "colorMode";
713 private static final String ATTR_LOCAL_ID = "localId";
714 private static final String ATTR_SERVICE_NAME = "serviceName";
716 private static final String ATTR_WIDTH_MILS = "widthMils";
717 private static final String ATTR_HEIGHT_MILS = "heightMils";
719 private static final String ATTR_HORIZONTAL_DPI = "horizontalDip";
720 private static final String ATTR_VERTICAL_DPI = "verticalDpi";
722 private static final String ATTR_LEFT_MILS = "leftMils";
723 private static final String ATTR_TOP_MILS = "topMils";
724 private static final String ATTR_RIGHT_MILS = "rightMils";
725 private static final String ATTR_BOTTOM_MILS = "bottomMils";
727 private static final String ATTR_START = "start";
728 private static final String ATTR_END = "end";
730 private static final String ATTR_NAME = "name";
731 private static final String ATTR_PAGE_COUNT = "pageCount";
732 private static final String ATTR_CONTENT_TYPE = "contentType";
734 private final AtomicFile mStatePersistFile;
736 private boolean mWriteStateScheduled;
738 private PersistenceManager() {
739 mStatePersistFile = new AtomicFile(new File(getFilesDir(),
743 public void writeStateLocked() {
744 if (!PERSISTNECE_MANAGER_ENABLED) {
747 if (mWriteStateScheduled) {
750 mWriteStateScheduled = true;
751 new AsyncTask<Void, Void, Void>() {
753 protected Void doInBackground(Void... params) {
754 synchronized (mLock) {
755 mWriteStateScheduled = false;
756 doWriteStateLocked();
760 }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
763 private void doWriteStateLocked() {
764 if (DEBUG_PERSISTENCE) {
765 Log.i(LOG_TAG, "[PERSIST START]");
767 FileOutputStream out = null;
769 out = mStatePersistFile.startWrite();
771 XmlSerializer serializer = new FastXmlSerializer();
772 serializer.setOutput(out, "utf-8");
773 serializer.startDocument(null, true);
774 serializer.startTag(null, TAG_SPOOLER);
776 List<PrintJobInfo> printJobs = mPrintJobs;
778 final int printJobCount = printJobs.size();
779 for (int j = 0; j < printJobCount; j++) {
780 PrintJobInfo printJob = printJobs.get(j);
782 serializer.startTag(null, TAG_JOB);
784 serializer.attribute(null, ATTR_ID, printJob.getId().flattenToString());
785 serializer.attribute(null, ATTR_LABEL, printJob.getLabel().toString());
786 serializer.attribute(null, ATTR_STATE, String.valueOf(printJob.getState()));
787 serializer.attribute(null, ATTR_APP_ID, String.valueOf(printJob.getAppId()));
788 String tag = printJob.getTag();
790 serializer.attribute(null, ATTR_TAG, tag);
792 serializer.attribute(null, ATTR_CREATION_TIME, String.valueOf(
793 printJob.getCreationTime()));
794 serializer.attribute(null, ATTR_COPIES, String.valueOf(printJob.getCopies()));
795 String printerName = printJob.getPrinterName();
796 if (!TextUtils.isEmpty(printerName)) {
797 serializer.attribute(null, ATTR_PRINTER_NAME, printerName);
799 String stateReason = printJob.getStateReason();
800 if (!TextUtils.isEmpty(stateReason)) {
801 serializer.attribute(null, ATTR_STATE_REASON, stateReason);
803 serializer.attribute(null, ATTR_CANCELLING, String.valueOf(
804 printJob.isCancelling()));
806 PrinterId printerId = printJob.getPrinterId();
807 if (printerId != null) {
808 serializer.startTag(null, TAG_PRINTER_ID);
809 serializer.attribute(null, ATTR_LOCAL_ID, printerId.getLocalId());
810 serializer.attribute(null, ATTR_SERVICE_NAME, printerId.getServiceName()
812 serializer.endTag(null, TAG_PRINTER_ID);
815 PageRange[] pages = printJob.getPages();
817 for (int i = 0; i < pages.length; i++) {
818 serializer.startTag(null, TAG_PAGE_RANGE);
819 serializer.attribute(null, ATTR_START, String.valueOf(
820 pages[i].getStart()));
821 serializer.attribute(null, ATTR_END, String.valueOf(
823 serializer.endTag(null, TAG_PAGE_RANGE);
827 PrintAttributes attributes = printJob.getAttributes();
828 if (attributes != null) {
829 serializer.startTag(null, TAG_ATTRIBUTES);
831 final int colorMode = attributes.getColorMode();
832 serializer.attribute(null, ATTR_COLOR_MODE,
833 String.valueOf(colorMode));
835 MediaSize mediaSize = attributes.getMediaSize();
836 if (mediaSize != null) {
837 serializer.startTag(null, TAG_MEDIA_SIZE);
838 serializer.attribute(null, ATTR_ID, mediaSize.getId());
839 serializer.attribute(null, ATTR_WIDTH_MILS, String.valueOf(
840 mediaSize.getWidthMils()));
841 serializer.attribute(null, ATTR_HEIGHT_MILS, String.valueOf(
842 mediaSize.getHeightMils()));
843 // We prefer to store only the package name and
844 // resource id and fallback to the label.
845 if (!TextUtils.isEmpty(mediaSize.mPackageName)
846 && mediaSize.mLabelResId > 0) {
847 serializer.attribute(null, ATTR_PACKAGE_NAME,
848 mediaSize.mPackageName);
849 serializer.attribute(null, ATTR_LABEL_RES_ID,
850 String.valueOf(mediaSize.mLabelResId));
852 serializer.attribute(null, ATTR_LABEL,
853 mediaSize.getLabel(getPackageManager()));
855 serializer.endTag(null, TAG_MEDIA_SIZE);
858 Resolution resolution = attributes.getResolution();
859 if (resolution != null) {
860 serializer.startTag(null, TAG_RESOLUTION);
861 serializer.attribute(null, ATTR_ID, resolution.getId());
862 serializer.attribute(null, ATTR_HORIZONTAL_DPI, String.valueOf(
863 resolution.getHorizontalDpi()));
864 serializer.attribute(null, ATTR_VERTICAL_DPI, String.valueOf(
865 resolution.getVerticalDpi()));
866 serializer.attribute(null, ATTR_LABEL,
867 resolution.getLabel());
868 serializer.endTag(null, TAG_RESOLUTION);
871 Margins margins = attributes.getMinMargins();
872 if (margins != null) {
873 serializer.startTag(null, TAG_MARGINS);
874 serializer.attribute(null, ATTR_LEFT_MILS, String.valueOf(
875 margins.getLeftMils()));
876 serializer.attribute(null, ATTR_TOP_MILS, String.valueOf(
877 margins.getTopMils()));
878 serializer.attribute(null, ATTR_RIGHT_MILS, String.valueOf(
879 margins.getRightMils()));
880 serializer.attribute(null, ATTR_BOTTOM_MILS, String.valueOf(
881 margins.getBottomMils()));
882 serializer.endTag(null, TAG_MARGINS);
885 serializer.endTag(null, TAG_ATTRIBUTES);
888 PrintDocumentInfo documentInfo = printJob.getDocumentInfo();
889 if (documentInfo != null) {
890 serializer.startTag(null, TAG_DOCUMENT_INFO);
891 serializer.attribute(null, ATTR_NAME, documentInfo.getName());
892 serializer.attribute(null, ATTR_CONTENT_TYPE, String.valueOf(
893 documentInfo.getContentType()));
894 serializer.attribute(null, ATTR_PAGE_COUNT, String.valueOf(
895 documentInfo.getPageCount()));
896 serializer.endTag(null, TAG_DOCUMENT_INFO);
899 serializer.endTag(null, TAG_JOB);
901 if (DEBUG_PERSISTENCE) {
902 Log.i(LOG_TAG, "[PERSISTED] " + printJob);
906 serializer.endTag(null, TAG_SPOOLER);
907 serializer.endDocument();
908 mStatePersistFile.finishWrite(out);
909 if (DEBUG_PERSISTENCE) {
910 Log.i(LOG_TAG, "[PERSIST END]");
912 } catch (IOException e) {
913 Slog.w(LOG_TAG, "Failed to write state, restoring backup.", e);
914 mStatePersistFile.failWrite(out);
916 IoUtils.closeQuietly(out);
920 public void readStateLocked() {
921 if (!PERSISTNECE_MANAGER_ENABLED) {
924 FileInputStream in = null;
926 in = mStatePersistFile.openRead();
927 } catch (FileNotFoundException e) {
928 Log.i(LOG_TAG, "No existing print spooler state.");
932 XmlPullParser parser = Xml.newPullParser();
933 parser.setInput(in, null);
935 } catch (IllegalStateException ise) {
936 Slog.w(LOG_TAG, "Failed parsing ", ise);
937 } catch (NullPointerException npe) {
938 Slog.w(LOG_TAG, "Failed parsing ", npe);
939 } catch (NumberFormatException nfe) {
940 Slog.w(LOG_TAG, "Failed parsing ", nfe);
941 } catch (XmlPullParserException xppe) {
942 Slog.w(LOG_TAG, "Failed parsing ", xppe);
943 } catch (IOException ioe) {
944 Slog.w(LOG_TAG, "Failed parsing ", ioe);
945 } catch (IndexOutOfBoundsException iobe) {
946 Slog.w(LOG_TAG, "Failed parsing ", iobe);
948 IoUtils.closeQuietly(in);
952 private void parseState(XmlPullParser parser)
953 throws IOException, XmlPullParserException {
955 skipEmptyTextTags(parser);
956 expect(parser, XmlPullParser.START_TAG, TAG_SPOOLER);
959 while (parsePrintJob(parser)) {
963 skipEmptyTextTags(parser);
964 expect(parser, XmlPullParser.END_TAG, TAG_SPOOLER);
967 private boolean parsePrintJob(XmlPullParser parser)
968 throws IOException, XmlPullParserException {
969 skipEmptyTextTags(parser);
970 if (!accept(parser, XmlPullParser.START_TAG, TAG_JOB)) {
974 PrintJobInfo printJob = new PrintJobInfo();
976 PrintJobId printJobId = PrintJobId.unflattenFromString(
977 parser.getAttributeValue(null, ATTR_ID));
978 printJob.setId(printJobId);
979 String label = parser.getAttributeValue(null, ATTR_LABEL);
980 printJob.setLabel(label);
981 final int state = Integer.parseInt(parser.getAttributeValue(null, ATTR_STATE));
982 printJob.setState(state);
983 final int appId = Integer.parseInt(parser.getAttributeValue(null, ATTR_APP_ID));
984 printJob.setAppId(appId);
985 String tag = parser.getAttributeValue(null, ATTR_TAG);
986 printJob.setTag(tag);
987 String creationTime = parser.getAttributeValue(null, ATTR_CREATION_TIME);
988 printJob.setCreationTime(Long.parseLong(creationTime));
989 String copies = parser.getAttributeValue(null, ATTR_COPIES);
990 printJob.setCopies(Integer.parseInt(copies));
991 String printerName = parser.getAttributeValue(null, ATTR_PRINTER_NAME);
992 printJob.setPrinterName(printerName);
993 String stateReason = parser.getAttributeValue(null, ATTR_STATE_REASON);
994 printJob.setStateReason(stateReason);
995 String cancelling = parser.getAttributeValue(null, ATTR_CANCELLING);
996 printJob.setCancelling(!TextUtils.isEmpty(cancelling)
997 ? Boolean.parseBoolean(cancelling) : false);
1001 skipEmptyTextTags(parser);
1002 if (accept(parser, XmlPullParser.START_TAG, TAG_PRINTER_ID)) {
1003 String localId = parser.getAttributeValue(null, ATTR_LOCAL_ID);
1004 ComponentName service = ComponentName.unflattenFromString(parser.getAttributeValue(
1005 null, ATTR_SERVICE_NAME));
1006 printJob.setPrinterId(new PrinterId(service, localId));
1008 skipEmptyTextTags(parser);
1009 expect(parser, XmlPullParser.END_TAG, TAG_PRINTER_ID);
1013 skipEmptyTextTags(parser);
1014 List<PageRange> pageRanges = null;
1015 while (accept(parser, XmlPullParser.START_TAG, TAG_PAGE_RANGE)) {
1016 final int start = Integer.parseInt(parser.getAttributeValue(null, ATTR_START));
1017 final int end = Integer.parseInt(parser.getAttributeValue(null, ATTR_END));
1018 PageRange pageRange = new PageRange(start, end);
1019 if (pageRanges == null) {
1020 pageRanges = new ArrayList<PageRange>();
1022 pageRanges.add(pageRange);
1024 skipEmptyTextTags(parser);
1025 expect(parser, XmlPullParser.END_TAG, TAG_PAGE_RANGE);
1028 if (pageRanges != null) {
1029 PageRange[] pageRangesArray = new PageRange[pageRanges.size()];
1030 pageRanges.toArray(pageRangesArray);
1031 printJob.setPages(pageRangesArray);
1034 skipEmptyTextTags(parser);
1035 if (accept(parser, XmlPullParser.START_TAG, TAG_ATTRIBUTES)) {
1037 PrintAttributes.Builder builder = new PrintAttributes.Builder();
1039 String colorMode = parser.getAttributeValue(null, ATTR_COLOR_MODE);
1040 builder.setColorMode(Integer.parseInt(colorMode));
1044 skipEmptyTextTags(parser);
1045 if (accept(parser, XmlPullParser.START_TAG, TAG_MEDIA_SIZE)) {
1046 String id = parser.getAttributeValue(null, ATTR_ID);
1047 label = parser.getAttributeValue(null, ATTR_LABEL);
1048 final int widthMils = Integer.parseInt(parser.getAttributeValue(null,
1050 final int heightMils = Integer.parseInt(parser.getAttributeValue(null,
1052 String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
1053 String labelResIdString = parser.getAttributeValue(null, ATTR_LABEL_RES_ID);
1054 final int labelResId = (labelResIdString != null)
1055 ? Integer.parseInt(labelResIdString) : 0;
1056 label = parser.getAttributeValue(null, ATTR_LABEL);
1057 MediaSize mediaSize = new MediaSize(id, label, packageName, labelResId,
1058 widthMils, heightMils);
1059 builder.setMediaSize(mediaSize);
1061 skipEmptyTextTags(parser);
1062 expect(parser, XmlPullParser.END_TAG, TAG_MEDIA_SIZE);
1066 skipEmptyTextTags(parser);
1067 if (accept(parser, XmlPullParser.START_TAG, TAG_RESOLUTION)) {
1068 String id = parser.getAttributeValue(null, ATTR_ID);
1069 label = parser.getAttributeValue(null, ATTR_LABEL);
1070 final int horizontalDpi = Integer.parseInt(parser.getAttributeValue(null,
1071 ATTR_HORIZONTAL_DPI));
1072 final int verticalDpi = Integer.parseInt(parser.getAttributeValue(null,
1073 ATTR_VERTICAL_DPI));
1074 Resolution resolution = new Resolution(id, label, horizontalDpi, verticalDpi);
1075 builder.setResolution(resolution);
1077 skipEmptyTextTags(parser);
1078 expect(parser, XmlPullParser.END_TAG, TAG_RESOLUTION);
1082 skipEmptyTextTags(parser);
1083 if (accept(parser, XmlPullParser.START_TAG, TAG_MARGINS)) {
1084 final int leftMils = Integer.parseInt(parser.getAttributeValue(null,
1086 final int topMils = Integer.parseInt(parser.getAttributeValue(null,
1088 final int rightMils = Integer.parseInt(parser.getAttributeValue(null,
1090 final int bottomMils = Integer.parseInt(parser.getAttributeValue(null,
1092 Margins margins = new Margins(leftMils, topMils, rightMils, bottomMils);
1093 builder.setMinMargins(margins);
1095 skipEmptyTextTags(parser);
1096 expect(parser, XmlPullParser.END_TAG, TAG_MARGINS);
1100 printJob.setAttributes(builder.build());
1102 skipEmptyTextTags(parser);
1103 expect(parser, XmlPullParser.END_TAG, TAG_ATTRIBUTES);
1107 skipEmptyTextTags(parser);
1108 if (accept(parser, XmlPullParser.START_TAG, TAG_DOCUMENT_INFO)) {
1109 String name = parser.getAttributeValue(null, ATTR_NAME);
1110 final int pageCount = Integer.parseInt(parser.getAttributeValue(null,
1112 final int contentType = Integer.parseInt(parser.getAttributeValue(null,
1113 ATTR_CONTENT_TYPE));
1114 PrintDocumentInfo info = new PrintDocumentInfo.Builder(name)
1115 .setPageCount(pageCount)
1116 .setContentType(contentType).build();
1117 printJob.setDocumentInfo(info);
1119 skipEmptyTextTags(parser);
1120 expect(parser, XmlPullParser.END_TAG, TAG_DOCUMENT_INFO);
1124 mPrintJobs.add(printJob);
1126 if (DEBUG_PERSISTENCE) {
1127 Log.i(LOG_TAG, "[RESTORED] " + printJob);
1130 skipEmptyTextTags(parser);
1131 expect(parser, XmlPullParser.END_TAG, TAG_JOB);
1136 private void expect(XmlPullParser parser, int type, String tag)
1137 throws IOException, XmlPullParserException {
1138 if (!accept(parser, type, tag)) {
1139 throw new XmlPullParserException("Exepected event: " + type
1140 + " and tag: " + tag + " but got event: " + parser.getEventType()
1141 + " and tag:" + parser.getName());
1145 private void skipEmptyTextTags(XmlPullParser parser)
1146 throws IOException, XmlPullParserException {
1147 while (accept(parser, XmlPullParser.TEXT, null)
1148 && "\n".equals(parser.getText())) {
1153 private boolean accept(XmlPullParser parser, int type, String tag)
1154 throws IOException, XmlPullParserException {
1155 if (parser.getEventType() != type) {
1159 if (!tag.equals(parser.getName())) {
1162 } else if (parser.getName() != null) {
1169 final class PrintSpooler extends IPrintSpooler.Stub {
1171 public void getPrintJobInfos(IPrintSpoolerCallbacks callback,
1172 ComponentName componentName, int state, int appId, int sequence)
1173 throws RemoteException {
1174 List<PrintJobInfo> printJobs = null;
1176 printJobs = PrintSpoolerService.this.getPrintJobInfos(
1177 componentName, state, appId);
1179 callback.onGetPrintJobInfosResult(printJobs, sequence);
1184 public void getPrintJobInfo(PrintJobId printJobId, IPrintSpoolerCallbacks callback,
1185 int appId, int sequence) throws RemoteException {
1186 PrintJobInfo printJob = null;
1188 printJob = PrintSpoolerService.this.getPrintJobInfo(printJobId, appId);
1190 callback.onGetPrintJobInfoResult(printJob, sequence);
1195 public void createPrintJob(PrintJobInfo printJob) {
1196 PrintSpoolerService.this.createPrintJob(printJob);
1200 public void setPrintJobState(PrintJobId printJobId, int state, String error,
1201 IPrintSpoolerCallbacks callback, int sequece) throws RemoteException {
1202 boolean success = false;
1204 success = PrintSpoolerService.this.setPrintJobState(
1205 printJobId, state, error);
1207 callback.onSetPrintJobStateResult(success, sequece);
1212 public void setPrintJobTag(PrintJobId printJobId, String tag,
1213 IPrintSpoolerCallbacks callback, int sequece) throws RemoteException {
1214 boolean success = false;
1216 success = PrintSpoolerService.this.setPrintJobTag(printJobId, tag);
1218 callback.onSetPrintJobTagResult(success, sequece);
1223 public void writePrintJobData(ParcelFileDescriptor fd, PrintJobId printJobId) {
1224 PrintSpoolerService.this.writePrintJobData(fd, printJobId);
1228 public void setClient(IPrintSpoolerClient client) {
1229 Message message = mHandlerCaller.obtainMessageO(
1230 HandlerCallerCallback.MSG_SET_CLIENT, client);
1231 mHandlerCaller.executeOrSendMessage(message);
1235 public void removeObsoletePrintJobs() {
1236 PrintSpoolerService.this.removeObsoletePrintJobs();
1240 protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
1241 PrintSpoolerService.this.dump(fd, writer, args);
1245 public void setPrintJobCancelling(PrintJobId printJobId, boolean cancelling) {
1246 PrintSpoolerService.this.setPrintJobCancelling(printJobId, cancelling);
1249 public PrintSpoolerService getService() {
1250 return PrintSpoolerService.this;