OSDN Git Service

Fix SystemUI crash on devices with WiFi only
[android-x86/frameworks-base.git] / packages / PrintSpooler / src / com / android / printspooler / PrintSpoolerService.java
1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package com.android.printspooler;
18
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;
51
52 import com.android.internal.os.HandlerCaller;
53 import com.android.internal.os.SomeArgs;
54 import com.android.internal.util.FastXmlSerializer;
55
56 import libcore.io.IoUtils;
57
58 import org.xmlpull.v1.XmlPullParser;
59 import org.xmlpull.v1.XmlPullParserException;
60 import org.xmlpull.v1.XmlSerializer;
61
62 import java.io.File;
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;
71
72 /**
73  * Service for exposing some of the {@link PrintSpooler} functionality to
74  * another process.
75  */
76 public final class PrintSpoolerService extends Service {
77
78     private static final String LOG_TAG = "PrintSpoolerService";
79
80     private static final boolean DEBUG_PRINT_JOB_LIFECYCLE = false;
81
82     private static final boolean DEBUG_PERSISTENCE = false;
83
84     private static final boolean PERSISTNECE_MANAGER_ENABLED = true;
85
86     private static final long CHECK_ALL_PRINTJOBS_HANDLED_DELAY = 5000;
87
88     private static final String PRINT_JOB_FILE_PREFIX = "print_job_";
89
90     private static final String PRINT_FILE_EXTENSION = "pdf";
91
92     private static final Object sLock = new Object();
93
94     private final Object mLock = new Object();
95
96     private final List<PrintJobInfo> mPrintJobs = new ArrayList<PrintJobInfo>();
97
98     private static PrintSpoolerService sInstance;
99
100     private IPrintSpoolerClient mClient;
101
102     private HandlerCaller mHandlerCaller;
103
104     private PersistenceManager mPersistanceManager;
105
106     private NotificationController mNotificationController;
107
108     public static PrintSpoolerService peekInstance() {
109         synchronized (sLock) {
110             return sInstance;
111         }
112     }
113
114     @Override
115     public void onCreate() {
116         super.onCreate();
117         mHandlerCaller = new HandlerCaller(this, getMainLooper(),
118                 new HandlerCallerCallback(), false);
119
120         mPersistanceManager = new PersistenceManager();
121         mNotificationController = new NotificationController(PrintSpoolerService.this);
122
123         synchronized (mLock) {
124             mPersistanceManager.readStateLocked();
125             handleReadPrintJobsLocked();
126         }
127
128         synchronized (sLock) {
129             sInstance = this;
130         }
131     }
132
133     @Override
134     public IBinder onBind(Intent intent) {
135         return new IPrintSpooler.Stub() {
136             @Override
137             public void getPrintJobInfos(IPrintSpoolerCallbacks callback,
138                     ComponentName componentName, int state, int appId, int sequence)
139                     throws RemoteException {
140                 List<PrintJobInfo> printJobs = null;
141                 try {
142                     printJobs = PrintSpoolerService.this.getPrintJobInfos(
143                             componentName, state, appId);
144                 } finally {
145                     callback.onGetPrintJobInfosResult(printJobs, sequence);
146                 }
147             }
148
149             @Override
150             public void getPrintJobInfo(PrintJobId printJobId, IPrintSpoolerCallbacks callback,
151                     int appId, int sequence) throws RemoteException {
152                 PrintJobInfo printJob = null;
153                 try {
154                     printJob = PrintSpoolerService.this.getPrintJobInfo(printJobId, appId);
155                 } finally {
156                     callback.onGetPrintJobInfoResult(printJob, sequence);
157                 }
158             }
159
160             @SuppressWarnings("deprecation")
161             @Override
162             public void createPrintJob(PrintJobInfo printJob, IPrintClient client,
163                 IPrintDocumentAdapter printAdapter) throws RemoteException {
164                 PrintSpoolerService.this.createPrintJob(printJob);
165
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);
171
172                 IntentSender sender = PendingIntent.getActivity(
173                         PrintSpoolerService.this, 0, intent, PendingIntent.FLAG_ONE_SHOT
174                         | PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender();
175
176                 Message message = mHandlerCaller.obtainMessageO(
177                         HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED,
178                         printJob);
179                 mHandlerCaller.executeOrSendMessage(message);
180
181                 message = mHandlerCaller.obtainMessageOO(
182                         HandlerCallerCallback.MSG_START_PRINT_JOB_CONFIG_ACTIVITY,
183                         client, sender);
184                 mHandlerCaller.executeOrSendMessage(message);
185
186                 printJob.setCreationTime(System.currentTimeMillis());
187             }
188
189             @Override
190             public void setPrintJobState(PrintJobId printJobId, int state, String error,
191                     IPrintSpoolerCallbacks callback, int sequece) throws RemoteException {
192                 boolean success = false;
193                 try {
194                     success = PrintSpoolerService.this.setPrintJobState(
195                             printJobId, state, error);
196                 } finally {
197                     callback.onSetPrintJobStateResult(success, sequece);
198                 }
199             }
200
201             @Override
202             public void setPrintJobTag(PrintJobId printJobId, String tag,
203                     IPrintSpoolerCallbacks callback, int sequece) throws RemoteException {
204                 boolean success = false;
205                 try {
206                     success = PrintSpoolerService.this.setPrintJobTag(printJobId, tag);
207                 } finally {
208                     callback.onSetPrintJobTagResult(success, sequece);
209                 }
210             }
211
212             @Override
213             public void writePrintJobData(ParcelFileDescriptor fd, PrintJobId printJobId) {
214                 PrintSpoolerService.this.writePrintJobData(fd, printJobId);
215             }
216
217             @Override
218             public void setClient(IPrintSpoolerClient client) {
219                 Message message = mHandlerCaller.obtainMessageO(
220                         HandlerCallerCallback.MSG_SET_CLIENT, client);
221                 mHandlerCaller.executeOrSendMessage(message);
222             }
223
224             @Override
225             public void removeObsoletePrintJobs() {
226                 PrintSpoolerService.this.removeObsoletePrintJobs();
227             }
228
229             @Override
230             protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
231                 PrintSpoolerService.this.dump(fd, writer, args);
232             }
233         };
234     }
235
236     @Override
237     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
238         synchronized (mLock) {
239             String prefix = (args.length > 0) ? args[0] : "";
240             String tab = "  ";
241
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());
247                 pw.println();
248             }
249
250             pw.append(prefix).append("print job files:").println();
251             File[] files = getFilesDir().listFiles();
252             if (files != null) {
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();
258                     }
259                 }
260             }
261         }
262     }
263
264     private void sendOnPrintJobQueued(PrintJobInfo printJob) {
265         Message message = mHandlerCaller.obtainMessageO(
266                 HandlerCallerCallback.MSG_ON_PRINT_JOB_QUEUED, printJob);
267         mHandlerCaller.executeOrSendMessage(message);
268     }
269
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);
274     }
275
276     private void sendOnAllPrintJobsHandled() {
277         Message message = mHandlerCaller.obtainMessage(
278                 HandlerCallerCallback.MSG_ON_ALL_PRINT_JOBS_HANDLED);
279         mHandlerCaller.executeOrSendMessage(message);
280     }
281
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;
290
291         @Override
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);
302                         }
303                     }
304                 } break;
305
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;
310                     args.recycle();
311                     try {
312                         client.startPrintJobConfigActivity(sender);
313                     } catch (RemoteException re) {
314                         Slog.i(LOG_TAG, "Error starting print job config activity!", re);
315                     }
316                 } break;
317
318                 case MSG_ON_PRINT_JOB_QUEUED: {
319                     PrintJobInfo printJob = (PrintJobInfo) message.obj;
320                     if (mClient != null) {
321                         try {
322                             mClient.onPrintJobQueued(printJob);
323                         } catch (RemoteException re) {
324                             Slog.e(LOG_TAG, "Error notify for a queued print job.", re);
325                         }
326                     }
327                 } break;
328
329                 case MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED: {
330                     ComponentName service = (ComponentName) message.obj;
331                     if (mClient != null) {
332                         try {
333                             mClient.onAllPrintJobsForServiceHandled(service);
334                         } catch (RemoteException re) {
335                             Slog.e(LOG_TAG, "Error notify for all print jobs per service"
336                                     + " handled.", re);
337                         }
338                     }
339                 } break;
340
341                 case MSG_ON_ALL_PRINT_JOBS_HANDLED: {
342                     if (mClient != null) {
343                         try {
344                             mClient.onAllPrintJobsHandled();
345                         } catch (RemoteException re) {
346                             Slog.e(LOG_TAG, "Error notify for all print job handled.", re);
347                         }
348                     }
349                 } break;
350
351                 case MSG_CHECK_ALL_PRINTJOBS_HANDLED: {
352                     checkAllPrintJobsHandled();
353                 } break;
354
355                 case MSG_ON_PRINT_JOB_STATE_CHANGED: {
356                     if (mClient != null) {
357                         PrintJobInfo printJob = (PrintJobInfo) message.obj;
358                         try {
359                             mClient.onPrintJobStateChanged(printJob);
360                         } catch (RemoteException re) {
361                             Slog.e(LOG_TAG, "Error notify for print job state change.", re);
362                         }
363                     }
364                 } break;
365             }
366         }
367     }
368
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>();
391                     }
392                     foundPrintJobs.add(printJob);
393                 }
394             }
395         }
396         return foundPrintJobs;
397     }
398
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));
403     }
404
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())) {
413                     return printJob;
414                 }
415             }
416             return null;
417         }
418     }
419
420     public void createPrintJob(PrintJobInfo printJob) {
421         synchronized (mLock) {
422             addPrintJobLocked(printJob);
423             setPrintJobState(printJob.getId(), PrintJobInfo.STATE_CREATED, null);
424         }
425     }
426
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();
435         if (files != null) {
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>();
442                     }
443                     String printJobIdString = file.getName().substring(
444                             PRINT_JOB_FILE_PREFIX.length(),
445                             file.getName().indexOf('.'));
446                     PrintJobId printJobId = PrintJobId.unflattenFromString(
447                             printJobIdString);
448                     fileForJobMap.put(printJobId, file);
449                 }
450             }
451         }
452
453         final int printJobCount = mPrintJobs.size();
454         for (int i = 0; i < printJobCount; i++) {
455             PrintJobInfo printJob = mPrintJobs.get(i);
456
457             // We want to have only the orphan files at the end.
458             if (fileForJobMap != null) {
459                 fileForJobMap.remove(printJob.getId());
460             }
461
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));
474                 } break;
475             }
476         }
477
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);
483                 file.delete();
484             }
485         }
486     }
487
488     public void checkAllPrintJobsHandled() {
489         synchronized (mLock) {
490             if (!hasActivePrintJobsLocked()) {
491                 notifyOnAllPrintJobsHandled();
492             }
493         }
494     }
495
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);
500         }
501         new AsyncTask<Void, Void, Void>() {
502             @Override
503             protected Void doInBackground(Void... params) {
504                 FileInputStream in = null;
505                 FileOutputStream out = null;
506                 try {
507                     if (printJob != null) {
508                         File file = generateFileForPrintJob(printJobId);
509                         in = new FileInputStream(file);
510                         out = new FileOutputStream(fd.getFileDescriptor());
511                     }
512                     final byte[] buffer = new byte[8192];
513                     while (true) {
514                         final int readByteCount = in.read(buffer);
515                         if (readByteCount < 0) {
516                             return null;
517                         }
518                         out.write(buffer, 0, readByteCount);
519                     }
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);
524                 } finally {
525                     IoUtils.closeQuietly(in);
526                     IoUtils.closeQuietly(out);
527                     IoUtils.closeQuietly(fd);
528                 }
529                 Log.i(LOG_TAG, "[END WRITE]");
530                 return null;
531             }
532         }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
533     }
534
535     public File generateFileForPrintJob(PrintJobId printJobId) {
536         return new File(getFilesDir(), PRINT_JOB_FILE_PREFIX
537                 + printJobId.flattenToString() + "." + PRINT_FILE_EXTENSION);
538     }
539
540     private void addPrintJobLocked(PrintJobInfo printJob) {
541         mPrintJobs.add(printJob);
542         if (DEBUG_PRINT_JOB_LIFECYCLE) {
543             Slog.i(LOG_TAG, "[ADD] " + printJob);
544         }
545     }
546
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());
556                     }
557                     removePrintJobFileLocked(printJob.getId());
558                 }
559             }
560             mPersistanceManager.writeStateLocked();
561         }
562     }
563
564     private void removePrintJobFileLocked(PrintJobId printJobId) {
565         File file = generateFileForPrintJob(printJobId);
566         if (file.exists()) {
567             file.delete();
568             if (DEBUG_PRINT_JOB_LIFECYCLE) {
569                 Slog.i(LOG_TAG, "[REMOVE FILE FOR] " + printJobId);
570             }
571         }
572     }
573
574     public boolean setPrintJobState(PrintJobId printJobId, int state, String error) {
575         boolean success = false;
576
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) {
582                     return false;
583                 }
584
585                 success = true;
586
587                 printJob.setState(state);
588                 printJob.setStateReason(error);
589                 mNotificationController.onPrintJobStateChanged(printJob);
590
591                 if (DEBUG_PRINT_JOB_LIFECYCLE) {
592                     Slog.i(LOG_TAG, "[STATE CHANGED] " + printJob);
593                 }
594
595                 switch (state) {
596                     case PrintJobInfo.STATE_COMPLETED:
597                     case PrintJobInfo.STATE_CANCELED:
598                         mPrintJobs.remove(printJob);
599                         removePrintJobFileLocked(printJob.getId());
600                         // $fall-through$
601
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);
608                             }
609                         }
610                     } break;
611
612                     case PrintJobInfo.STATE_QUEUED: {
613                         sendOnPrintJobQueued(new PrintJobInfo(printJob));
614                     }  break;
615                 }
616
617                 if (shouldPersistPrintJob(printJob)) {
618                     mPersistanceManager.writeStateLocked();
619                 }
620
621                 if (!hasActivePrintJobsLocked()) {
622                     notifyOnAllPrintJobsHandled();
623                 }
624
625                 Message message = mHandlerCaller.obtainMessageO(
626                         HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED,
627                         printJob);
628                 mHandlerCaller.executeOrSendMessage(message);
629             }
630         }
631
632         return success;
633     }
634
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())) {
640                 return true;
641             }
642         }
643         return false;
644     }
645
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)) {
652                 return true;
653             }
654         }
655         return false;
656     }
657
658     private boolean isObsoleteState(int printJobState) {
659         return (isTeminalState(printJobState)
660                 || printJobState == PrintJobInfo.STATE_QUEUED);
661     }
662
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;
668     }
669
670     private boolean isTeminalState(int printJobState) {
671         return printJobState == PrintJobInfo.STATE_COMPLETED
672                 || printJobState == PrintJobInfo.STATE_CANCELED;
673     }
674
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) {
681                     if (tag == null) {
682                         return false;
683                     }
684                 } else if (printJobTag.equals(tag)) {
685                     return false;
686                 }
687                 printJob.setTag(tag);
688                 if (shouldPersistPrintJob(printJob)) {
689                     mPersistanceManager.writeStateLocked();
690                 }
691                 return true;
692             }
693         }
694         return false;
695     }
696
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);
702             }
703         }
704     }
705
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);
712             }
713         }
714     }
715
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);
722             }
723         }
724     }
725
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());
732             }
733         }
734     }
735
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);
741             }
742         }
743     }
744
745     private boolean shouldPersistPrintJob(PrintJobInfo printJob) {
746         return printJob.getState() >= PrintJobInfo.STATE_QUEUED;
747     }
748
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
753         // completes.
754         new AsyncTask<Void, Void, Void>() {
755             @Override
756             protected Void doInBackground(Void... params) {
757                 sendOnAllPrintJobsHandled();
758                 return null;
759             }
760         }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
761     }
762
763     private final class PersistenceManager {
764         private static final String PERSIST_FILE_NAME = "print_spooler_state.xml";
765
766         private static final String TAG_SPOOLER = "spooler";
767         private static final String TAG_JOB = "job";
768
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";
773
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";
786
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";
790
791         private static final String ATTR_COLOR_MODE = "colorMode";
792
793         private static final String ATTR_LOCAL_ID = "localId";
794         private static final String ATTR_SERVICE_NAME = "serviceName";
795
796         private static final String ATTR_WIDTH_MILS = "widthMils";
797         private static final String ATTR_HEIGHT_MILS = "heightMils";
798
799         private static final String ATTR_HORIZONTAL_DPI = "horizontalDip";
800         private static final String ATTR_VERTICAL_DPI = "verticalDpi";
801
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";
806
807         private static final String ATTR_START = "start";
808         private static final String ATTR_END = "end";
809
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";
813
814         private final AtomicFile mStatePersistFile;
815
816         private boolean mWriteStateScheduled;
817
818         private PersistenceManager() {
819             mStatePersistFile = new AtomicFile(new File(getFilesDir(),
820                     PERSIST_FILE_NAME));
821         }
822
823         public void writeStateLocked() {
824             if (!PERSISTNECE_MANAGER_ENABLED) {
825                 return;
826             }
827             if (mWriteStateScheduled) {
828                 return;
829             }
830             mWriteStateScheduled = true;
831             new AsyncTask<Void, Void, Void>() {
832                 @Override
833                 protected Void doInBackground(Void... params) {
834                     synchronized (mLock) {
835                         mWriteStateScheduled = false;
836                         doWriteStateLocked();
837                     }
838                     return null;
839                 }
840             }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
841         }
842
843         private void doWriteStateLocked() {
844             if (DEBUG_PERSISTENCE) {
845                 Log.i(LOG_TAG, "[PERSIST START]");
846             }
847             FileOutputStream out = null;
848             try {
849                 out = mStatePersistFile.startWrite();
850
851                 XmlSerializer serializer = new FastXmlSerializer();
852                 serializer.setOutput(out, "utf-8");
853                 serializer.startDocument(null, true);
854                 serializer.startTag(null, TAG_SPOOLER);
855
856                 List<PrintJobInfo> printJobs = mPrintJobs;
857
858                 final int printJobCount = printJobs.size();
859                 for (int j = 0; j < printJobCount; j++) {
860                     PrintJobInfo printJob = printJobs.get(j);
861
862                     serializer.startTag(null, TAG_JOB);
863
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();
870                     if (tag != null) {
871                         serializer.attribute(null, ATTR_TAG, tag);
872                     }
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);
879                     }
880                     String stateReason = printJob.getStateReason();
881                     if (!TextUtils.isEmpty(stateReason)) {
882                         serializer.attribute(null, ATTR_STATE_REASON, stateReason);
883                     }
884
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()
890                                 .flattenToString());
891                         serializer.endTag(null, TAG_PRINTER_ID);
892                     }
893
894                     PageRange[] pages = printJob.getPages();
895                     if (pages != null) {
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(
901                                     pages[i].getEnd()));
902                             serializer.endTag(null, TAG_PAGE_RANGE);
903                         }
904                     }
905
906                     PrintAttributes attributes = printJob.getAttributes();
907                     if (attributes != null) {
908                         serializer.startTag(null, TAG_ATTRIBUTES);
909
910                         final int colorMode = attributes.getColorMode();
911                         serializer.attribute(null, ATTR_COLOR_MODE,
912                                 String.valueOf(colorMode));
913
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));
930                             } else {
931                                 serializer.attribute(null, ATTR_LABEL,
932                                         mediaSize.getLabel(getPackageManager()));
933                             }
934                             serializer.endTag(null, TAG_MEDIA_SIZE);
935                         }
936
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);
948                         }
949
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);
962                         }
963
964                         serializer.endTag(null, TAG_ATTRIBUTES);
965                     }
966
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);
976                     }
977
978                     serializer.endTag(null, TAG_JOB);
979
980                     if (DEBUG_PERSISTENCE) {
981                         Log.i(LOG_TAG, "[PERSISTED] " + printJob);
982                     }
983                 }
984
985                 serializer.endTag(null, TAG_SPOOLER);
986                 serializer.endDocument();
987                 mStatePersistFile.finishWrite(out);
988                 if (DEBUG_PERSISTENCE) {
989                     Log.i(LOG_TAG, "[PERSIST END]");
990                 }
991             } catch (IOException e) {
992                 Slog.w(LOG_TAG, "Failed to write state, restoring backup.", e);
993                 mStatePersistFile.failWrite(out);
994             } finally {
995                 IoUtils.closeQuietly(out);
996             }
997         }
998
999         public void readStateLocked() {
1000             if (!PERSISTNECE_MANAGER_ENABLED) {
1001                 return;
1002             }
1003             FileInputStream in = null;
1004             try {
1005                 in = mStatePersistFile.openRead();
1006             } catch (FileNotFoundException e) {
1007                 Log.i(LOG_TAG, "No existing print spooler state.");
1008                 return;
1009             }
1010             try {
1011                 XmlPullParser parser = Xml.newPullParser();
1012                 parser.setInput(in, null);
1013                 parseState(parser);
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);
1026             } finally {
1027                 IoUtils.closeQuietly(in);
1028             }
1029         }
1030
1031         private void parseState(XmlPullParser parser)
1032                 throws IOException, XmlPullParserException {
1033             parser.next();
1034             skipEmptyTextTags(parser);
1035             expect(parser, XmlPullParser.START_TAG, TAG_SPOOLER);
1036             parser.next();
1037
1038             while (parsePrintJob(parser)) {
1039                 parser.next();
1040             }
1041
1042             skipEmptyTextTags(parser);
1043             expect(parser, XmlPullParser.END_TAG, TAG_SPOOLER);
1044         }
1045
1046         private boolean parsePrintJob(XmlPullParser parser)
1047                 throws IOException, XmlPullParserException {
1048             skipEmptyTextTags(parser);
1049             if (!accept(parser, XmlPullParser.START_TAG, TAG_JOB)) {
1050                 return false;
1051             }
1052
1053             PrintJobInfo printJob = new PrintJobInfo();
1054
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);
1076
1077             parser.next();
1078
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));
1085                 parser.next();
1086                 skipEmptyTextTags(parser);
1087                 expect(parser, XmlPullParser.END_TAG, TAG_PRINTER_ID);
1088                 parser.next();
1089             }
1090
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>();
1099                 }
1100                 pageRanges.add(pageRange);
1101                 parser.next();
1102                 skipEmptyTextTags(parser);
1103                 expect(parser, XmlPullParser.END_TAG, TAG_PAGE_RANGE);
1104                 parser.next();
1105             }
1106             if (pageRanges != null) {
1107                 PageRange[] pageRangesArray = new PageRange[pageRanges.size()];
1108                 pageRanges.toArray(pageRangesArray);
1109                 printJob.setPages(pageRangesArray);
1110             }
1111
1112             skipEmptyTextTags(parser);
1113             if (accept(parser, XmlPullParser.START_TAG, TAG_ATTRIBUTES)) {
1114
1115                 PrintAttributes.Builder builder = new PrintAttributes.Builder();
1116
1117                 String colorMode = parser.getAttributeValue(null, ATTR_COLOR_MODE);
1118                 builder.setColorMode(Integer.parseInt(colorMode));
1119
1120                 parser.next();
1121
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,
1127                             ATTR_WIDTH_MILS));
1128                     final int heightMils = Integer.parseInt(parser.getAttributeValue(null,
1129                             ATTR_HEIGHT_MILS));
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);
1138                     parser.next();
1139                     skipEmptyTextTags(parser);
1140                     expect(parser, XmlPullParser.END_TAG, TAG_MEDIA_SIZE);
1141                     parser.next();
1142                 }
1143
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);
1154                     parser.next();
1155                     skipEmptyTextTags(parser);
1156                     expect(parser, XmlPullParser.END_TAG, TAG_RESOLUTION);
1157                     parser.next();
1158                 }
1159
1160                 skipEmptyTextTags(parser);
1161                 if (accept(parser, XmlPullParser.START_TAG, TAG_MARGINS)) {
1162                     final int leftMils = Integer.parseInt(parser.getAttributeValue(null,
1163                             ATTR_LEFT_MILS));
1164                     final int topMils = Integer.parseInt(parser.getAttributeValue(null,
1165                             ATTR_TOP_MILS));
1166                     final int rightMils = Integer.parseInt(parser.getAttributeValue(null,
1167                             ATTR_RIGHT_MILS));
1168                     final int bottomMils = Integer.parseInt(parser.getAttributeValue(null,
1169                             ATTR_BOTTOM_MILS));
1170                     Margins margins = new Margins(leftMils, topMils, rightMils, bottomMils);
1171                     builder.setMinMargins(margins);
1172                     parser.next();
1173                     skipEmptyTextTags(parser);
1174                     expect(parser, XmlPullParser.END_TAG, TAG_MARGINS);
1175                     parser.next();
1176                 }
1177
1178                 printJob.setAttributes(builder.build());
1179
1180                 skipEmptyTextTags(parser);
1181                 expect(parser, XmlPullParser.END_TAG, TAG_ATTRIBUTES);
1182                 parser.next();
1183             }
1184
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,
1189                         ATTR_PAGE_COUNT));
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);
1196                 parser.next();
1197                 skipEmptyTextTags(parser);
1198                 expect(parser, XmlPullParser.END_TAG, TAG_DOCUMENT_INFO);
1199                 parser.next();
1200             }
1201
1202             mPrintJobs.add(printJob);
1203
1204             if (DEBUG_PERSISTENCE) {
1205                 Log.i(LOG_TAG, "[RESTORED] " + printJob);
1206             }
1207
1208             skipEmptyTextTags(parser);
1209             expect(parser, XmlPullParser.END_TAG, TAG_JOB);
1210
1211             return true;
1212         }
1213
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());
1220             }
1221         }
1222
1223         private void skipEmptyTextTags(XmlPullParser parser)
1224                 throws IOException, XmlPullParserException {
1225             while (accept(parser, XmlPullParser.TEXT, null)
1226                     && "\n".equals(parser.getText())) {
1227                 parser.next();
1228             }
1229         }
1230
1231         private boolean accept(XmlPullParser parser, int type, String tag)
1232                 throws IOException, XmlPullParserException {
1233             if (parser.getEventType() != type) {
1234                 return false;
1235             }
1236             if (tag != null) {
1237                 if (!tag.equals(parser.getName())) {
1238                     return false;
1239                 }
1240             } else if (parser.getName() != null) {
1241                 return false;
1242             }
1243             return true;
1244         }
1245     }
1246 }