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.PendingIntent;
20 import android.app.Service;
21 import android.content.ComponentName;
22 import android.content.Intent;
23 import android.content.IntentSender;
24 import android.os.AsyncTask;
25 import android.os.IBinder;
26 import android.os.Message;
27 import android.os.ParcelFileDescriptor;
28 import android.os.RemoteException;
29 import android.print.IPrintClient;
30 import android.print.IPrintDocumentAdapter;
31 import android.print.IPrintSpooler;
32 import android.print.IPrintSpoolerCallbacks;
33 import android.print.IPrintSpoolerClient;
34 import android.print.PageRange;
35 import android.print.PrintAttributes;
36 import android.print.PrintAttributes.Margins;
37 import android.print.PrintAttributes.MediaSize;
38 import android.print.PrintAttributes.Resolution;
39 import android.print.PrintDocumentInfo;
40 import android.print.PrintJobId;
41 import android.print.PrintJobInfo;
42 import android.print.PrintManager;
43 import android.print.PrinterId;
44 import android.print.PrinterInfo;
45 import android.text.TextUtils;
46 import android.util.ArrayMap;
47 import android.util.AtomicFile;
48 import android.util.Log;
49 import android.util.Slog;
50 import android.util.Xml;
52 import com.android.internal.os.HandlerCaller;
53 import com.android.internal.os.SomeArgs;
54 import com.android.internal.util.FastXmlSerializer;
56 import libcore.io.IoUtils;
58 import org.xmlpull.v1.XmlPullParser;
59 import org.xmlpull.v1.XmlPullParserException;
60 import org.xmlpull.v1.XmlSerializer;
63 import java.io.FileDescriptor;
64 import java.io.FileInputStream;
65 import java.io.FileNotFoundException;
66 import java.io.FileOutputStream;
67 import java.io.IOException;
68 import java.io.PrintWriter;
69 import java.util.ArrayList;
70 import java.util.List;
73 * Service for exposing some of the {@link PrintSpooler} functionality to
76 public final class PrintSpoolerService extends Service {
78 private static final String LOG_TAG = "PrintSpoolerService";
80 private static final boolean DEBUG_PRINT_JOB_LIFECYCLE = false;
82 private static final boolean DEBUG_PERSISTENCE = false;
84 private static final boolean PERSISTNECE_MANAGER_ENABLED = true;
86 private static final long CHECK_ALL_PRINTJOBS_HANDLED_DELAY = 5000;
88 private static final String PRINT_JOB_FILE_PREFIX = "print_job_";
90 private static final String PRINT_FILE_EXTENSION = "pdf";
92 private static final Object sLock = new Object();
94 private final Object mLock = new Object();
96 private final List<PrintJobInfo> mPrintJobs = new ArrayList<PrintJobInfo>();
98 private static PrintSpoolerService sInstance;
100 private IPrintSpoolerClient mClient;
102 private HandlerCaller mHandlerCaller;
104 private PersistenceManager mPersistanceManager;
106 private NotificationController mNotificationController;
108 public static PrintSpoolerService peekInstance() {
109 synchronized (sLock) {
115 public void onCreate() {
117 mHandlerCaller = new HandlerCaller(this, getMainLooper(),
118 new HandlerCallerCallback(), false);
120 mPersistanceManager = new PersistenceManager();
121 mNotificationController = new NotificationController(PrintSpoolerService.this);
123 synchronized (mLock) {
124 mPersistanceManager.readStateLocked();
125 handleReadPrintJobsLocked();
128 synchronized (sLock) {
134 public IBinder onBind(Intent intent) {
135 return new IPrintSpooler.Stub() {
137 public void getPrintJobInfos(IPrintSpoolerCallbacks callback,
138 ComponentName componentName, int state, int appId, int sequence)
139 throws RemoteException {
140 List<PrintJobInfo> printJobs = null;
142 printJobs = PrintSpoolerService.this.getPrintJobInfos(
143 componentName, state, appId);
145 callback.onGetPrintJobInfosResult(printJobs, sequence);
150 public void getPrintJobInfo(PrintJobId printJobId, IPrintSpoolerCallbacks callback,
151 int appId, int sequence) throws RemoteException {
152 PrintJobInfo printJob = null;
154 printJob = PrintSpoolerService.this.getPrintJobInfo(printJobId, appId);
156 callback.onGetPrintJobInfoResult(printJob, sequence);
160 @SuppressWarnings("deprecation")
162 public void createPrintJob(PrintJobInfo printJob, IPrintClient client,
163 IPrintDocumentAdapter printAdapter) throws RemoteException {
164 PrintSpoolerService.this.createPrintJob(printJob);
166 Intent intent = new Intent(printJob.getId().flattenToString());
167 intent.setClass(PrintSpoolerService.this, PrintJobConfigActivity.class);
168 intent.putExtra(PrintJobConfigActivity.EXTRA_PRINT_DOCUMENT_ADAPTER,
169 printAdapter.asBinder());
170 intent.putExtra(PrintJobConfigActivity.EXTRA_PRINT_JOB, printJob);
172 IntentSender sender = PendingIntent.getActivity(
173 PrintSpoolerService.this, 0, intent, PendingIntent.FLAG_ONE_SHOT
174 | PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender();
176 Message message = mHandlerCaller.obtainMessageO(
177 HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED,
179 mHandlerCaller.executeOrSendMessage(message);
181 message = mHandlerCaller.obtainMessageOO(
182 HandlerCallerCallback.MSG_START_PRINT_JOB_CONFIG_ACTIVITY,
184 mHandlerCaller.executeOrSendMessage(message);
186 printJob.setCreationTime(System.currentTimeMillis());
190 public void setPrintJobState(PrintJobId printJobId, int state, String error,
191 IPrintSpoolerCallbacks callback, int sequece) throws RemoteException {
192 boolean success = false;
194 success = PrintSpoolerService.this.setPrintJobState(
195 printJobId, state, error);
197 callback.onSetPrintJobStateResult(success, sequece);
202 public void setPrintJobTag(PrintJobId printJobId, String tag,
203 IPrintSpoolerCallbacks callback, int sequece) throws RemoteException {
204 boolean success = false;
206 success = PrintSpoolerService.this.setPrintJobTag(printJobId, tag);
208 callback.onSetPrintJobTagResult(success, sequece);
213 public void writePrintJobData(ParcelFileDescriptor fd, PrintJobId printJobId) {
214 PrintSpoolerService.this.writePrintJobData(fd, printJobId);
218 public void setClient(IPrintSpoolerClient client) {
219 Message message = mHandlerCaller.obtainMessageO(
220 HandlerCallerCallback.MSG_SET_CLIENT, client);
221 mHandlerCaller.executeOrSendMessage(message);
225 public void removeObsoletePrintJobs() {
226 PrintSpoolerService.this.removeObsoletePrintJobs();
230 protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
231 PrintSpoolerService.this.dump(fd, writer, args);
237 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
238 synchronized (mLock) {
239 String prefix = (args.length > 0) ? args[0] : "";
242 pw.append(prefix).append("print jobs:").println();
243 final int printJobCount = mPrintJobs.size();
244 for (int i = 0; i < printJobCount; i++) {
245 PrintJobInfo printJob = mPrintJobs.get(i);
246 pw.append(prefix).append(tab).append(printJob.toString());
250 pw.append(prefix).append("print job files:").println();
251 File[] files = getFilesDir().listFiles();
253 final int fileCount = files.length;
254 for (int i = 0; i < fileCount; i++) {
255 File file = files[i];
256 if (file.isFile() && file.getName().startsWith(PRINT_JOB_FILE_PREFIX)) {
257 pw.append(prefix).append(tab).append(file.getName()).println();
264 private void sendOnPrintJobQueued(PrintJobInfo printJob) {
265 Message message = mHandlerCaller.obtainMessageO(
266 HandlerCallerCallback.MSG_ON_PRINT_JOB_QUEUED, printJob);
267 mHandlerCaller.executeOrSendMessage(message);
270 private void sendOnAllPrintJobsForServiceHandled(ComponentName service) {
271 Message message = mHandlerCaller.obtainMessageO(
272 HandlerCallerCallback.MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED, service);
273 mHandlerCaller.executeOrSendMessage(message);
276 private void sendOnAllPrintJobsHandled() {
277 Message message = mHandlerCaller.obtainMessage(
278 HandlerCallerCallback.MSG_ON_ALL_PRINT_JOBS_HANDLED);
279 mHandlerCaller.executeOrSendMessage(message);
282 private final class HandlerCallerCallback implements HandlerCaller.Callback {
283 public static final int MSG_SET_CLIENT = 1;
284 public static final int MSG_START_PRINT_JOB_CONFIG_ACTIVITY = 2;
285 public static final int MSG_ON_PRINT_JOB_QUEUED = 3;
286 public static final int MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED = 4;
287 public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 5;
288 public static final int MSG_CHECK_ALL_PRINTJOBS_HANDLED = 6;
289 public static final int MSG_ON_PRINT_JOB_STATE_CHANGED = 7;
292 public void executeMessage(Message message) {
293 switch (message.what) {
294 case MSG_SET_CLIENT: {
295 synchronized (mLock) {
296 mClient = (IPrintSpoolerClient) message.obj;
297 if (mClient != null) {
298 Message msg = mHandlerCaller.obtainMessage(
299 HandlerCallerCallback.MSG_CHECK_ALL_PRINTJOBS_HANDLED);
300 mHandlerCaller.sendMessageDelayed(msg,
301 CHECK_ALL_PRINTJOBS_HANDLED_DELAY);
306 case MSG_START_PRINT_JOB_CONFIG_ACTIVITY: {
307 SomeArgs args = (SomeArgs) message.obj;
308 IPrintClient client = (IPrintClient) args.arg1;
309 IntentSender sender = (IntentSender) args.arg2;
312 client.startPrintJobConfigActivity(sender);
313 } catch (RemoteException re) {
314 Slog.i(LOG_TAG, "Error starting print job config activity!", re);
318 case MSG_ON_PRINT_JOB_QUEUED: {
319 PrintJobInfo printJob = (PrintJobInfo) message.obj;
320 if (mClient != null) {
322 mClient.onPrintJobQueued(printJob);
323 } catch (RemoteException re) {
324 Slog.e(LOG_TAG, "Error notify for a queued print job.", re);
329 case MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED: {
330 ComponentName service = (ComponentName) message.obj;
331 if (mClient != null) {
333 mClient.onAllPrintJobsForServiceHandled(service);
334 } catch (RemoteException re) {
335 Slog.e(LOG_TAG, "Error notify for all print jobs per service"
341 case MSG_ON_ALL_PRINT_JOBS_HANDLED: {
342 if (mClient != null) {
344 mClient.onAllPrintJobsHandled();
345 } catch (RemoteException re) {
346 Slog.e(LOG_TAG, "Error notify for all print job handled.", re);
351 case MSG_CHECK_ALL_PRINTJOBS_HANDLED: {
352 checkAllPrintJobsHandled();
355 case MSG_ON_PRINT_JOB_STATE_CHANGED: {
356 if (mClient != null) {
357 PrintJobInfo printJob = (PrintJobInfo) message.obj;
359 mClient.onPrintJobStateChanged(printJob);
360 } catch (RemoteException re) {
361 Slog.e(LOG_TAG, "Error notify for print job state change.", re);
369 public List<PrintJobInfo> getPrintJobInfos(ComponentName componentName,
370 int state, int appId) {
371 List<PrintJobInfo> foundPrintJobs = null;
372 synchronized (mLock) {
373 final int printJobCount = mPrintJobs.size();
374 for (int i = 0; i < printJobCount; i++) {
375 PrintJobInfo printJob = mPrintJobs.get(i);
376 PrinterId printerId = printJob.getPrinterId();
377 final boolean sameComponent = (componentName == null
378 || (printerId != null
379 && componentName.equals(printerId.getServiceName())));
380 final boolean sameAppId = appId == PrintManager.APP_ID_ANY
381 || printJob.getAppId() == appId;
382 final boolean sameState = (state == printJob.getState())
383 || (state == PrintJobInfo.STATE_ANY)
384 || (state == PrintJobInfo.STATE_ANY_VISIBLE_TO_CLIENTS
385 && isStateVisibleToUser(printJob.getState()))
386 || (state == PrintJobInfo.STATE_ANY_ACTIVE
387 && isActiveState(printJob.getState()));
388 if (sameComponent && sameAppId && sameState) {
389 if (foundPrintJobs == null) {
390 foundPrintJobs = new ArrayList<PrintJobInfo>();
392 foundPrintJobs.add(printJob);
396 return foundPrintJobs;
399 private boolean isStateVisibleToUser(int state) {
400 return (isActiveState(state) && (state == PrintJobInfo.STATE_FAILED
401 || state == PrintJobInfo.STATE_COMPLETED || state == PrintJobInfo.STATE_CANCELED
402 || state == PrintJobInfo.STATE_BLOCKED));
405 public PrintJobInfo getPrintJobInfo(PrintJobId printJobId, int appId) {
406 synchronized (mLock) {
407 final int printJobCount = mPrintJobs.size();
408 for (int i = 0; i < printJobCount; i++) {
409 PrintJobInfo printJob = mPrintJobs.get(i);
410 if (printJob.getId().equals(printJobId)
411 && (appId == PrintManager.APP_ID_ANY
412 || appId == printJob.getAppId())) {
420 public void createPrintJob(PrintJobInfo printJob) {
421 synchronized (mLock) {
422 addPrintJobLocked(printJob);
423 setPrintJobState(printJob.getId(), PrintJobInfo.STATE_CREATED, null);
427 private void handleReadPrintJobsLocked() {
428 // Make a map with the files for a print job since we may have
429 // to delete some. One example of getting orphan files if the
430 // spooler crashes while constructing a print job. We do not
431 // persist partially populated print jobs under construction to
432 // avoid special handling for various attributes missing.
433 ArrayMap<PrintJobId, File> fileForJobMap = null;
434 File[] files = getFilesDir().listFiles();
436 final int fileCount = files.length;
437 for (int i = 0; i < fileCount; i++) {
438 File file = files[i];
439 if (file.isFile() && file.getName().startsWith(PRINT_JOB_FILE_PREFIX)) {
440 if (fileForJobMap == null) {
441 fileForJobMap = new ArrayMap<PrintJobId, File>();
443 String printJobIdString = file.getName().substring(
444 PRINT_JOB_FILE_PREFIX.length(),
445 file.getName().indexOf('.'));
446 PrintJobId printJobId = PrintJobId.unflattenFromString(
448 fileForJobMap.put(printJobId, file);
453 final int printJobCount = mPrintJobs.size();
454 for (int i = 0; i < printJobCount; i++) {
455 PrintJobInfo printJob = mPrintJobs.get(i);
457 // We want to have only the orphan files at the end.
458 if (fileForJobMap != null) {
459 fileForJobMap.remove(printJob.getId());
462 // Update the notification.
463 mNotificationController.onPrintJobStateChanged(printJob);
464 switch (printJob.getState()) {
465 case PrintJobInfo.STATE_QUEUED:
466 case PrintJobInfo.STATE_STARTED:
467 case PrintJobInfo.STATE_BLOCKED: {
468 // We have a print job that was queued or started or blocked in
469 // the past but the device battery died or a crash occurred. In
470 // this case we assume the print job failed and let the user
471 // decide whether to restart the job or just cancel it.
472 setPrintJobState(printJob.getId(), PrintJobInfo.STATE_FAILED,
473 getString(R.string.no_connection_to_printer));
478 // Delete the orphan files.
479 if (fileForJobMap != null) {
480 final int orphanFileCount = fileForJobMap.size();
481 for (int i = 0; i < orphanFileCount; i++) {
482 File file = fileForJobMap.valueAt(i);
488 public void checkAllPrintJobsHandled() {
489 synchronized (mLock) {
490 if (!hasActivePrintJobsLocked()) {
491 notifyOnAllPrintJobsHandled();
496 public void writePrintJobData(final ParcelFileDescriptor fd, final PrintJobId printJobId) {
497 final PrintJobInfo printJob;
498 synchronized (mLock) {
499 printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
501 new AsyncTask<Void, Void, Void>() {
503 protected Void doInBackground(Void... params) {
504 FileInputStream in = null;
505 FileOutputStream out = null;
507 if (printJob != null) {
508 File file = generateFileForPrintJob(printJobId);
509 in = new FileInputStream(file);
510 out = new FileOutputStream(fd.getFileDescriptor());
512 final byte[] buffer = new byte[8192];
514 final int readByteCount = in.read(buffer);
515 if (readByteCount < 0) {
518 out.write(buffer, 0, readByteCount);
520 } catch (FileNotFoundException fnfe) {
521 Log.e(LOG_TAG, "Error writing print job data!", fnfe);
522 } catch (IOException ioe) {
523 Log.e(LOG_TAG, "Error writing print job data!", ioe);
525 IoUtils.closeQuietly(in);
526 IoUtils.closeQuietly(out);
527 IoUtils.closeQuietly(fd);
529 Log.i(LOG_TAG, "[END WRITE]");
532 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
535 public File generateFileForPrintJob(PrintJobId printJobId) {
536 return new File(getFilesDir(), PRINT_JOB_FILE_PREFIX
537 + printJobId.flattenToString() + "." + PRINT_FILE_EXTENSION);
540 private void addPrintJobLocked(PrintJobInfo printJob) {
541 mPrintJobs.add(printJob);
542 if (DEBUG_PRINT_JOB_LIFECYCLE) {
543 Slog.i(LOG_TAG, "[ADD] " + printJob);
547 private void removeObsoletePrintJobs() {
548 synchronized (mLock) {
549 final int printJobCount = mPrintJobs.size();
550 for (int i = printJobCount - 1; i >= 0; i--) {
551 PrintJobInfo printJob = mPrintJobs.get(i);
552 if (isObsoleteState(printJob.getState())) {
553 mPrintJobs.remove(i);
554 if (DEBUG_PRINT_JOB_LIFECYCLE) {
555 Slog.i(LOG_TAG, "[REMOVE] " + printJob.getId().flattenToString());
557 removePrintJobFileLocked(printJob.getId());
560 mPersistanceManager.writeStateLocked();
564 private void removePrintJobFileLocked(PrintJobId printJobId) {
565 File file = generateFileForPrintJob(printJobId);
568 if (DEBUG_PRINT_JOB_LIFECYCLE) {
569 Slog.i(LOG_TAG, "[REMOVE FILE FOR] " + printJobId);
574 public boolean setPrintJobState(PrintJobId printJobId, int state, String error) {
575 boolean success = false;
577 synchronized (mLock) {
578 PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
579 if (printJob != null) {
580 final int oldState = printJob.getState();
581 if (oldState == state) {
587 printJob.setState(state);
588 printJob.setStateReason(error);
589 mNotificationController.onPrintJobStateChanged(printJob);
591 if (DEBUG_PRINT_JOB_LIFECYCLE) {
592 Slog.i(LOG_TAG, "[STATE CHANGED] " + printJob);
596 case PrintJobInfo.STATE_COMPLETED:
597 case PrintJobInfo.STATE_CANCELED:
598 mPrintJobs.remove(printJob);
599 removePrintJobFileLocked(printJob.getId());
602 case PrintJobInfo.STATE_FAILED: {
603 PrinterId printerId = printJob.getPrinterId();
604 if (printerId != null) {
605 ComponentName service = printerId.getServiceName();
606 if (!hasActivePrintJobsForServiceLocked(service)) {
607 sendOnAllPrintJobsForServiceHandled(service);
612 case PrintJobInfo.STATE_QUEUED: {
613 sendOnPrintJobQueued(new PrintJobInfo(printJob));
617 if (shouldPersistPrintJob(printJob)) {
618 mPersistanceManager.writeStateLocked();
621 if (!hasActivePrintJobsLocked()) {
622 notifyOnAllPrintJobsHandled();
625 Message message = mHandlerCaller.obtainMessageO(
626 HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED,
628 mHandlerCaller.executeOrSendMessage(message);
635 public boolean hasActivePrintJobsLocked() {
636 final int printJobCount = mPrintJobs.size();
637 for (int i = 0; i < printJobCount; i++) {
638 PrintJobInfo printJob = mPrintJobs.get(i);
639 if (isActiveState(printJob.getState())) {
646 public boolean hasActivePrintJobsForServiceLocked(ComponentName service) {
647 final int printJobCount = mPrintJobs.size();
648 for (int i = 0; i < printJobCount; i++) {
649 PrintJobInfo printJob = mPrintJobs.get(i);
650 if (isActiveState(printJob.getState())
651 && printJob.getPrinterId().getServiceName().equals(service)) {
658 private boolean isObsoleteState(int printJobState) {
659 return (isTeminalState(printJobState)
660 || printJobState == PrintJobInfo.STATE_QUEUED);
663 private boolean isActiveState(int printJobState) {
664 return printJobState == PrintJobInfo.STATE_CREATED
665 || printJobState == PrintJobInfo.STATE_QUEUED
666 || printJobState == PrintJobInfo.STATE_STARTED
667 || printJobState == PrintJobInfo.STATE_BLOCKED;
670 private boolean isTeminalState(int printJobState) {
671 return printJobState == PrintJobInfo.STATE_COMPLETED
672 || printJobState == PrintJobInfo.STATE_CANCELED;
675 public boolean setPrintJobTag(PrintJobId printJobId, String tag) {
676 synchronized (mLock) {
677 PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
678 if (printJob != null) {
679 String printJobTag = printJob.getTag();
680 if (printJobTag == null) {
684 } else if (printJobTag.equals(tag)) {
687 printJob.setTag(tag);
688 if (shouldPersistPrintJob(printJob)) {
689 mPersistanceManager.writeStateLocked();
697 public void setPrintJobCopiesNoPersistence(PrintJobId printJobId, int copies) {
698 synchronized (mLock) {
699 PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
700 if (printJob != null) {
701 printJob.setCopies(copies);
706 public void setPrintJobPrintDocumentInfoNoPersistence(PrintJobId printJobId,
707 PrintDocumentInfo info) {
708 synchronized (mLock) {
709 PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
710 if (printJob != null) {
711 printJob.setDocumentInfo(info);
716 public void setPrintJobAttributesNoPersistence(PrintJobId printJobId,
717 PrintAttributes attributes) {
718 synchronized (mLock) {
719 PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
720 if (printJob != null) {
721 printJob.setAttributes(attributes);
726 public void setPrintJobPrinterNoPersistence(PrintJobId printJobId, PrinterInfo printer) {
727 synchronized (mLock) {
728 PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
729 if (printJob != null) {
730 printJob.setPrinterId(printer.getId());
731 printJob.setPrinterName(printer.getName());
736 public void setPrintJobPagesNoPersistence(PrintJobId printJobId, PageRange[] pages) {
737 synchronized (mLock) {
738 PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
739 if (printJob != null) {
740 printJob.setPages(pages);
745 private boolean shouldPersistPrintJob(PrintJobInfo printJob) {
746 return printJob.getState() >= PrintJobInfo.STATE_QUEUED;
749 private void notifyOnAllPrintJobsHandled() {
750 // This has to run on the tread that is persisting the current state
751 // since this call may result in the system unbinding from the spooler
752 // and as a result the spooler process may get killed before the write
754 new AsyncTask<Void, Void, Void>() {
756 protected Void doInBackground(Void... params) {
757 sendOnAllPrintJobsHandled();
760 }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
763 private final class PersistenceManager {
764 private static final String PERSIST_FILE_NAME = "print_spooler_state.xml";
766 private static final String TAG_SPOOLER = "spooler";
767 private static final String TAG_JOB = "job";
769 private static final String TAG_PRINTER_ID = "printerId";
770 private static final String TAG_PAGE_RANGE = "pageRange";
771 private static final String TAG_ATTRIBUTES = "attributes";
772 private static final String TAG_DOCUMENT_INFO = "documentInfo";
774 private static final String ATTR_ID = "id";
775 private static final String ATTR_LABEL = "label";
776 private static final String ATTR_LABEL_RES_ID = "labelResId";
777 private static final String ATTR_PACKAGE_NAME = "packageName";
778 private static final String ATTR_STATE = "state";
779 private static final String ATTR_APP_ID = "appId";
780 private static final String ATTR_USER_ID = "userId";
781 private static final String ATTR_TAG = "tag";
782 private static final String ATTR_CREATION_TIME = "creationTime";
783 private static final String ATTR_COPIES = "copies";
784 private static final String ATTR_PRINTER_NAME = "printerName";
785 private static final String ATTR_STATE_REASON = "stateReason";
787 private static final String TAG_MEDIA_SIZE = "mediaSize";
788 private static final String TAG_RESOLUTION = "resolution";
789 private static final String TAG_MARGINS = "margins";
791 private static final String ATTR_COLOR_MODE = "colorMode";
793 private static final String ATTR_LOCAL_ID = "localId";
794 private static final String ATTR_SERVICE_NAME = "serviceName";
796 private static final String ATTR_WIDTH_MILS = "widthMils";
797 private static final String ATTR_HEIGHT_MILS = "heightMils";
799 private static final String ATTR_HORIZONTAL_DPI = "horizontalDip";
800 private static final String ATTR_VERTICAL_DPI = "verticalDpi";
802 private static final String ATTR_LEFT_MILS = "leftMils";
803 private static final String ATTR_TOP_MILS = "topMils";
804 private static final String ATTR_RIGHT_MILS = "rightMils";
805 private static final String ATTR_BOTTOM_MILS = "bottomMils";
807 private static final String ATTR_START = "start";
808 private static final String ATTR_END = "end";
810 private static final String ATTR_NAME = "name";
811 private static final String ATTR_PAGE_COUNT = "pageCount";
812 private static final String ATTR_CONTENT_TYPE = "contentType";
814 private final AtomicFile mStatePersistFile;
816 private boolean mWriteStateScheduled;
818 private PersistenceManager() {
819 mStatePersistFile = new AtomicFile(new File(getFilesDir(),
823 public void writeStateLocked() {
824 if (!PERSISTNECE_MANAGER_ENABLED) {
827 if (mWriteStateScheduled) {
830 mWriteStateScheduled = true;
831 new AsyncTask<Void, Void, Void>() {
833 protected Void doInBackground(Void... params) {
834 synchronized (mLock) {
835 mWriteStateScheduled = false;
836 doWriteStateLocked();
840 }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
843 private void doWriteStateLocked() {
844 if (DEBUG_PERSISTENCE) {
845 Log.i(LOG_TAG, "[PERSIST START]");
847 FileOutputStream out = null;
849 out = mStatePersistFile.startWrite();
851 XmlSerializer serializer = new FastXmlSerializer();
852 serializer.setOutput(out, "utf-8");
853 serializer.startDocument(null, true);
854 serializer.startTag(null, TAG_SPOOLER);
856 List<PrintJobInfo> printJobs = mPrintJobs;
858 final int printJobCount = printJobs.size();
859 for (int j = 0; j < printJobCount; j++) {
860 PrintJobInfo printJob = printJobs.get(j);
862 serializer.startTag(null, TAG_JOB);
864 serializer.attribute(null, ATTR_ID, printJob.getId().flattenToString());
865 serializer.attribute(null, ATTR_LABEL, printJob.getLabel().toString());
866 serializer.attribute(null, ATTR_STATE, String.valueOf(printJob.getState()));
867 serializer.attribute(null, ATTR_APP_ID, String.valueOf(printJob.getAppId()));
868 serializer.attribute(null, ATTR_USER_ID, String.valueOf(printJob.getUserId()));
869 String tag = printJob.getTag();
871 serializer.attribute(null, ATTR_TAG, tag);
873 serializer.attribute(null, ATTR_CREATION_TIME, String.valueOf(
874 printJob.getCreationTime()));
875 serializer.attribute(null, ATTR_COPIES, String.valueOf(printJob.getCopies()));
876 String printerName = printJob.getPrinterName();
877 if (!TextUtils.isEmpty(printerName)) {
878 serializer.attribute(null, ATTR_PRINTER_NAME, printerName);
880 String stateReason = printJob.getStateReason();
881 if (!TextUtils.isEmpty(stateReason)) {
882 serializer.attribute(null, ATTR_STATE_REASON, stateReason);
885 PrinterId printerId = printJob.getPrinterId();
886 if (printerId != null) {
887 serializer.startTag(null, TAG_PRINTER_ID);
888 serializer.attribute(null, ATTR_LOCAL_ID, printerId.getLocalId());
889 serializer.attribute(null, ATTR_SERVICE_NAME, printerId.getServiceName()
891 serializer.endTag(null, TAG_PRINTER_ID);
894 PageRange[] pages = printJob.getPages();
896 for (int i = 0; i < pages.length; i++) {
897 serializer.startTag(null, TAG_PAGE_RANGE);
898 serializer.attribute(null, ATTR_START, String.valueOf(
899 pages[i].getStart()));
900 serializer.attribute(null, ATTR_END, String.valueOf(
902 serializer.endTag(null, TAG_PAGE_RANGE);
906 PrintAttributes attributes = printJob.getAttributes();
907 if (attributes != null) {
908 serializer.startTag(null, TAG_ATTRIBUTES);
910 final int colorMode = attributes.getColorMode();
911 serializer.attribute(null, ATTR_COLOR_MODE,
912 String.valueOf(colorMode));
914 MediaSize mediaSize = attributes.getMediaSize();
915 if (mediaSize != null) {
916 serializer.startTag(null, TAG_MEDIA_SIZE);
917 serializer.attribute(null, ATTR_ID, mediaSize.getId());
918 serializer.attribute(null, ATTR_WIDTH_MILS, String.valueOf(
919 mediaSize.getWidthMils()));
920 serializer.attribute(null, ATTR_HEIGHT_MILS, String.valueOf(
921 mediaSize.getHeightMils()));
922 // We prefer to store only the package name and
923 // resource id and fallback to the label.
924 if (!TextUtils.isEmpty(mediaSize.mPackageName)
925 && mediaSize.mLabelResId > 0) {
926 serializer.attribute(null, ATTR_PACKAGE_NAME,
927 mediaSize.mPackageName);
928 serializer.attribute(null, ATTR_LABEL_RES_ID,
929 String.valueOf(mediaSize.mLabelResId));
931 serializer.attribute(null, ATTR_LABEL,
932 mediaSize.getLabel(getPackageManager()));
934 serializer.endTag(null, TAG_MEDIA_SIZE);
937 Resolution resolution = attributes.getResolution();
938 if (resolution != null) {
939 serializer.startTag(null, TAG_RESOLUTION);
940 serializer.attribute(null, ATTR_ID, resolution.getId());
941 serializer.attribute(null, ATTR_HORIZONTAL_DPI, String.valueOf(
942 resolution.getHorizontalDpi()));
943 serializer.attribute(null, ATTR_VERTICAL_DPI, String.valueOf(
944 resolution.getVerticalDpi()));
945 serializer.attribute(null, ATTR_LABEL,
946 resolution.getLabel());
947 serializer.endTag(null, TAG_RESOLUTION);
950 Margins margins = attributes.getMinMargins();
951 if (margins != null) {
952 serializer.startTag(null, TAG_MARGINS);
953 serializer.attribute(null, ATTR_LEFT_MILS, String.valueOf(
954 margins.getLeftMils()));
955 serializer.attribute(null, ATTR_TOP_MILS, String.valueOf(
956 margins.getTopMils()));
957 serializer.attribute(null, ATTR_RIGHT_MILS, String.valueOf(
958 margins.getRightMils()));
959 serializer.attribute(null, ATTR_BOTTOM_MILS, String.valueOf(
960 margins.getBottomMils()));
961 serializer.endTag(null, TAG_MARGINS);
964 serializer.endTag(null, TAG_ATTRIBUTES);
967 PrintDocumentInfo documentInfo = printJob.getDocumentInfo();
968 if (documentInfo != null) {
969 serializer.startTag(null, TAG_DOCUMENT_INFO);
970 serializer.attribute(null, ATTR_NAME, documentInfo.getName());
971 serializer.attribute(null, ATTR_CONTENT_TYPE, String.valueOf(
972 documentInfo.getContentType()));
973 serializer.attribute(null, ATTR_PAGE_COUNT, String.valueOf(
974 documentInfo.getPageCount()));
975 serializer.endTag(null, TAG_DOCUMENT_INFO);
978 serializer.endTag(null, TAG_JOB);
980 if (DEBUG_PERSISTENCE) {
981 Log.i(LOG_TAG, "[PERSISTED] " + printJob);
985 serializer.endTag(null, TAG_SPOOLER);
986 serializer.endDocument();
987 mStatePersistFile.finishWrite(out);
988 if (DEBUG_PERSISTENCE) {
989 Log.i(LOG_TAG, "[PERSIST END]");
991 } catch (IOException e) {
992 Slog.w(LOG_TAG, "Failed to write state, restoring backup.", e);
993 mStatePersistFile.failWrite(out);
995 IoUtils.closeQuietly(out);
999 public void readStateLocked() {
1000 if (!PERSISTNECE_MANAGER_ENABLED) {
1003 FileInputStream in = null;
1005 in = mStatePersistFile.openRead();
1006 } catch (FileNotFoundException e) {
1007 Log.i(LOG_TAG, "No existing print spooler state.");
1011 XmlPullParser parser = Xml.newPullParser();
1012 parser.setInput(in, null);
1014 } catch (IllegalStateException ise) {
1015 Slog.w(LOG_TAG, "Failed parsing ", ise);
1016 } catch (NullPointerException npe) {
1017 Slog.w(LOG_TAG, "Failed parsing ", npe);
1018 } catch (NumberFormatException nfe) {
1019 Slog.w(LOG_TAG, "Failed parsing ", nfe);
1020 } catch (XmlPullParserException xppe) {
1021 Slog.w(LOG_TAG, "Failed parsing ", xppe);
1022 } catch (IOException ioe) {
1023 Slog.w(LOG_TAG, "Failed parsing ", ioe);
1024 } catch (IndexOutOfBoundsException iobe) {
1025 Slog.w(LOG_TAG, "Failed parsing ", iobe);
1027 IoUtils.closeQuietly(in);
1031 private void parseState(XmlPullParser parser)
1032 throws IOException, XmlPullParserException {
1034 skipEmptyTextTags(parser);
1035 expect(parser, XmlPullParser.START_TAG, TAG_SPOOLER);
1038 while (parsePrintJob(parser)) {
1042 skipEmptyTextTags(parser);
1043 expect(parser, XmlPullParser.END_TAG, TAG_SPOOLER);
1046 private boolean parsePrintJob(XmlPullParser parser)
1047 throws IOException, XmlPullParserException {
1048 skipEmptyTextTags(parser);
1049 if (!accept(parser, XmlPullParser.START_TAG, TAG_JOB)) {
1053 PrintJobInfo printJob = new PrintJobInfo();
1055 PrintJobId printJobId = PrintJobId.unflattenFromString(
1056 parser.getAttributeValue(null, ATTR_ID));
1057 printJob.setId(printJobId);
1058 String label = parser.getAttributeValue(null, ATTR_LABEL);
1059 printJob.setLabel(label);
1060 final int state = Integer.parseInt(parser.getAttributeValue(null, ATTR_STATE));
1061 printJob.setState(state);
1062 final int appId = Integer.parseInt(parser.getAttributeValue(null, ATTR_APP_ID));
1063 printJob.setAppId(appId);
1064 final int userId = Integer.parseInt(parser.getAttributeValue(null, ATTR_USER_ID));
1065 printJob.setUserId(userId);
1066 String tag = parser.getAttributeValue(null, ATTR_TAG);
1067 printJob.setTag(tag);
1068 String creationTime = parser.getAttributeValue(null, ATTR_CREATION_TIME);
1069 printJob.setCreationTime(Long.parseLong(creationTime));
1070 String copies = parser.getAttributeValue(null, ATTR_COPIES);
1071 printJob.setCopies(Integer.parseInt(copies));
1072 String printerName = parser.getAttributeValue(null, ATTR_PRINTER_NAME);
1073 printJob.setPrinterName(printerName);
1074 String stateReason = parser.getAttributeValue(null, ATTR_STATE_REASON);
1075 printJob.setStateReason(stateReason);
1079 skipEmptyTextTags(parser);
1080 if (accept(parser, XmlPullParser.START_TAG, TAG_PRINTER_ID)) {
1081 String localId = parser.getAttributeValue(null, ATTR_LOCAL_ID);
1082 ComponentName service = ComponentName.unflattenFromString(parser.getAttributeValue(
1083 null, ATTR_SERVICE_NAME));
1084 printJob.setPrinterId(new PrinterId(service, localId));
1086 skipEmptyTextTags(parser);
1087 expect(parser, XmlPullParser.END_TAG, TAG_PRINTER_ID);
1091 skipEmptyTextTags(parser);
1092 List<PageRange> pageRanges = null;
1093 while (accept(parser, XmlPullParser.START_TAG, TAG_PAGE_RANGE)) {
1094 final int start = Integer.parseInt(parser.getAttributeValue(null, ATTR_START));
1095 final int end = Integer.parseInt(parser.getAttributeValue(null, ATTR_END));
1096 PageRange pageRange = new PageRange(start, end);
1097 if (pageRanges == null) {
1098 pageRanges = new ArrayList<PageRange>();
1100 pageRanges.add(pageRange);
1102 skipEmptyTextTags(parser);
1103 expect(parser, XmlPullParser.END_TAG, TAG_PAGE_RANGE);
1106 if (pageRanges != null) {
1107 PageRange[] pageRangesArray = new PageRange[pageRanges.size()];
1108 pageRanges.toArray(pageRangesArray);
1109 printJob.setPages(pageRangesArray);
1112 skipEmptyTextTags(parser);
1113 if (accept(parser, XmlPullParser.START_TAG, TAG_ATTRIBUTES)) {
1115 PrintAttributes.Builder builder = new PrintAttributes.Builder();
1117 String colorMode = parser.getAttributeValue(null, ATTR_COLOR_MODE);
1118 builder.setColorMode(Integer.parseInt(colorMode));
1122 skipEmptyTextTags(parser);
1123 if (accept(parser, XmlPullParser.START_TAG, TAG_MEDIA_SIZE)) {
1124 String id = parser.getAttributeValue(null, ATTR_ID);
1125 label = parser.getAttributeValue(null, ATTR_LABEL);
1126 final int widthMils = Integer.parseInt(parser.getAttributeValue(null,
1128 final int heightMils = Integer.parseInt(parser.getAttributeValue(null,
1130 String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
1131 String labelResIdString = parser.getAttributeValue(null, ATTR_LABEL_RES_ID);
1132 final int labelResId = (labelResIdString != null)
1133 ? Integer.parseInt(labelResIdString) : 0;
1134 label = parser.getAttributeValue(null, ATTR_LABEL);
1135 MediaSize mediaSize = new MediaSize(id, label, packageName, labelResId,
1136 widthMils, heightMils);
1137 builder.setMediaSize(mediaSize);
1139 skipEmptyTextTags(parser);
1140 expect(parser, XmlPullParser.END_TAG, TAG_MEDIA_SIZE);
1144 skipEmptyTextTags(parser);
1145 if (accept(parser, XmlPullParser.START_TAG, TAG_RESOLUTION)) {
1146 String id = parser.getAttributeValue(null, ATTR_ID);
1147 label = parser.getAttributeValue(null, ATTR_LABEL);
1148 final int horizontalDpi = Integer.parseInt(parser.getAttributeValue(null,
1149 ATTR_HORIZONTAL_DPI));
1150 final int verticalDpi = Integer.parseInt(parser.getAttributeValue(null,
1151 ATTR_VERTICAL_DPI));
1152 Resolution resolution = new Resolution(id, label, horizontalDpi, verticalDpi);
1153 builder.setResolution(resolution);
1155 skipEmptyTextTags(parser);
1156 expect(parser, XmlPullParser.END_TAG, TAG_RESOLUTION);
1160 skipEmptyTextTags(parser);
1161 if (accept(parser, XmlPullParser.START_TAG, TAG_MARGINS)) {
1162 final int leftMils = Integer.parseInt(parser.getAttributeValue(null,
1164 final int topMils = Integer.parseInt(parser.getAttributeValue(null,
1166 final int rightMils = Integer.parseInt(parser.getAttributeValue(null,
1168 final int bottomMils = Integer.parseInt(parser.getAttributeValue(null,
1170 Margins margins = new Margins(leftMils, topMils, rightMils, bottomMils);
1171 builder.setMinMargins(margins);
1173 skipEmptyTextTags(parser);
1174 expect(parser, XmlPullParser.END_TAG, TAG_MARGINS);
1178 printJob.setAttributes(builder.build());
1180 skipEmptyTextTags(parser);
1181 expect(parser, XmlPullParser.END_TAG, TAG_ATTRIBUTES);
1185 skipEmptyTextTags(parser);
1186 if (accept(parser, XmlPullParser.START_TAG, TAG_DOCUMENT_INFO)) {
1187 String name = parser.getAttributeValue(null, ATTR_NAME);
1188 final int pageCount = Integer.parseInt(parser.getAttributeValue(null,
1190 final int contentType = Integer.parseInt(parser.getAttributeValue(null,
1191 ATTR_CONTENT_TYPE));
1192 PrintDocumentInfo info = new PrintDocumentInfo.Builder(name)
1193 .setPageCount(pageCount)
1194 .setContentType(contentType).build();
1195 printJob.setDocumentInfo(info);
1197 skipEmptyTextTags(parser);
1198 expect(parser, XmlPullParser.END_TAG, TAG_DOCUMENT_INFO);
1202 mPrintJobs.add(printJob);
1204 if (DEBUG_PERSISTENCE) {
1205 Log.i(LOG_TAG, "[RESTORED] " + printJob);
1208 skipEmptyTextTags(parser);
1209 expect(parser, XmlPullParser.END_TAG, TAG_JOB);
1214 private void expect(XmlPullParser parser, int type, String tag)
1215 throws IOException, XmlPullParserException {
1216 if (!accept(parser, type, tag)) {
1217 throw new XmlPullParserException("Exepected event: " + type
1218 + " and tag: " + tag + " but got event: " + parser.getEventType()
1219 + " and tag:" + parser.getName());
1223 private void skipEmptyTextTags(XmlPullParser parser)
1224 throws IOException, XmlPullParserException {
1225 while (accept(parser, XmlPullParser.TEXT, null)
1226 && "\n".equals(parser.getText())) {
1231 private boolean accept(XmlPullParser parser, int type, String tag)
1232 throws IOException, XmlPullParserException {
1233 if (parser.getEventType() != type) {
1237 if (!tag.equals(parser.getName())) {
1240 } else if (parser.getName() != null) {