OSDN Git Service

Merge "Change hasCustomPrintIcon and setStatus as requested by API council." into...
[android-x86/frameworks-base.git] / packages / PrintSpooler / src / com / android / printspooler / model / 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.model;
18
19 import android.annotation.FloatRange;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.StringRes;
23 import android.app.Service;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.graphics.drawable.Icon;
28 import android.os.AsyncTask;
29 import android.os.Bundle;
30 import android.os.IBinder;
31 import android.os.Message;
32 import android.os.ParcelFileDescriptor;
33 import android.os.RemoteException;
34 import android.print.IPrintSpooler;
35 import android.print.IPrintSpoolerCallbacks;
36 import android.print.IPrintSpoolerClient;
37 import android.print.PageRange;
38 import android.print.PrintAttributes;
39 import android.print.PrintAttributes.Margins;
40 import android.print.PrintAttributes.MediaSize;
41 import android.print.PrintAttributes.Resolution;
42 import android.print.PrintDocumentInfo;
43 import android.print.PrintJobId;
44 import android.print.PrintJobInfo;
45 import android.print.PrintManager;
46 import android.print.PrinterId;
47 import android.text.TextUtils;
48 import android.util.ArrayMap;
49 import android.util.AtomicFile;
50 import android.util.Log;
51 import android.util.Slog;
52 import android.util.Xml;
53
54 import com.android.internal.logging.MetricsLogger;
55 import com.android.internal.os.HandlerCaller;
56 import com.android.internal.util.FastXmlSerializer;
57 import com.android.printspooler.R;
58 import com.android.printspooler.util.ApprovedPrintServices;
59
60 import libcore.io.IoUtils;
61
62 import org.xmlpull.v1.XmlPullParser;
63 import org.xmlpull.v1.XmlPullParserException;
64 import org.xmlpull.v1.XmlSerializer;
65
66 import java.io.File;
67 import java.io.FileDescriptor;
68 import java.io.FileInputStream;
69 import java.io.FileNotFoundException;
70 import java.io.FileOutputStream;
71 import java.io.IOException;
72 import java.io.PrintWriter;
73 import java.nio.charset.StandardCharsets;
74 import java.util.ArrayList;
75 import java.util.List;
76 import java.util.Set;
77
78 /**
79  * Service for exposing some of the {@link PrintSpooler} functionality to
80  * another process.
81  */
82 public final class PrintSpoolerService extends Service {
83
84     private static final String LOG_TAG = "PrintSpoolerService";
85
86     private static final boolean DEBUG_PRINT_JOB_LIFECYCLE = false;
87
88     private static final boolean DEBUG_PERSISTENCE = false;
89
90     private static final boolean PERSISTENCE_MANAGER_ENABLED = true;
91
92     private static final long CHECK_ALL_PRINTJOBS_HANDLED_DELAY = 5000;
93
94     private static final String PRINT_JOB_FILE_PREFIX = "print_job_";
95
96     private static final String PRINT_FILE_EXTENSION = "pdf";
97
98     private static final Object sLock = new Object();
99
100     private final Object mLock = new Object();
101
102     private final List<PrintJobInfo> mPrintJobs = new ArrayList<>();
103
104     private static PrintSpoolerService sInstance;
105
106     private IPrintSpoolerClient mClient;
107
108     private HandlerCaller mHandlerCaller;
109
110     private PersistenceManager mPersistanceManager;
111
112     private NotificationController mNotificationController;
113
114     /** Cache for custom printer icons loaded from the print service */
115     private CustomPrinterIconCache mCustomIconCache;
116
117     public static PrintSpoolerService peekInstance() {
118         synchronized (sLock) {
119             return sInstance;
120         }
121     }
122
123     @Override
124     public void onCreate() {
125         super.onCreate();
126         mHandlerCaller = new HandlerCaller(this, getMainLooper(),
127                 new HandlerCallerCallback(), false);
128
129         mPersistanceManager = new PersistenceManager();
130         mNotificationController = new NotificationController(PrintSpoolerService.this);
131         mCustomIconCache = new CustomPrinterIconCache(getCacheDir());
132
133         synchronized (mLock) {
134             mPersistanceManager.readStateLocked();
135             handleReadPrintJobsLocked();
136         }
137
138         synchronized (sLock) {
139             sInstance = this;
140         }
141     }
142
143     @Override
144     public void onDestroy() {
145         super.onDestroy();
146     }
147
148     @Override
149     public IBinder onBind(Intent intent) {
150         return new PrintSpooler();
151     }
152
153     @Override
154     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
155         String prefix = (args.length > 0) ? args[0] : "";
156         String tab = "  ";
157
158         synchronized (mLock) {
159             pw.append(prefix).append("print jobs:").println();
160             final int printJobCount = mPrintJobs.size();
161             for (int i = 0; i < printJobCount; i++) {
162                 PrintJobInfo printJob = mPrintJobs.get(i);
163                 pw.append(prefix).append(tab).append(printJob.toString());
164                 pw.println();
165             }
166
167             pw.append(prefix).append("print job files:").println();
168             File[] files = getFilesDir().listFiles();
169             if (files != null) {
170                 final int fileCount = files.length;
171                 for (int i = 0; i < fileCount; i++) {
172                     File file = files[i];
173                     if (file.isFile() && file.getName().startsWith(PRINT_JOB_FILE_PREFIX)) {
174                         pw.append(prefix).append(tab).append(file.getName()).println();
175                     }
176                 }
177             }
178         }
179
180         pw.append(prefix).append("approved print services:").println();
181         Set<String> approvedPrintServices = (new ApprovedPrintServices(this)).getApprovedServices();
182         if (approvedPrintServices != null) {
183             for (String approvedService : approvedPrintServices) {
184                 pw.append(prefix).append(tab).append(approvedService).println();
185             }
186         }
187     }
188
189     private void sendOnPrintJobQueued(PrintJobInfo printJob) {
190         Message message = mHandlerCaller.obtainMessageO(
191                 HandlerCallerCallback.MSG_ON_PRINT_JOB_QUEUED, printJob);
192         mHandlerCaller.executeOrSendMessage(message);
193     }
194
195     private void sendOnAllPrintJobsForServiceHandled(ComponentName service) {
196         Message message = mHandlerCaller.obtainMessageO(
197                 HandlerCallerCallback.MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED, service);
198         mHandlerCaller.executeOrSendMessage(message);
199     }
200
201     private void sendOnAllPrintJobsHandled() {
202         Message message = mHandlerCaller.obtainMessage(
203                 HandlerCallerCallback.MSG_ON_ALL_PRINT_JOBS_HANDLED);
204         mHandlerCaller.executeOrSendMessage(message);
205     }
206
207     private final class HandlerCallerCallback implements HandlerCaller.Callback {
208         public static final int MSG_SET_CLIENT = 1;
209         public static final int MSG_ON_PRINT_JOB_QUEUED = 2;
210         public static final int MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED = 3;
211         public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 4;
212         public static final int MSG_CHECK_ALL_PRINTJOBS_HANDLED = 5;
213         public static final int MSG_ON_PRINT_JOB_STATE_CHANGED = 6;
214
215         @Override
216         public void executeMessage(Message message) {
217             switch (message.what) {
218                 case MSG_SET_CLIENT: {
219                     synchronized (mLock) {
220                         mClient = (IPrintSpoolerClient) message.obj;
221                         if (mClient != null) {
222                             Message msg = mHandlerCaller.obtainMessage(
223                                     HandlerCallerCallback.MSG_CHECK_ALL_PRINTJOBS_HANDLED);
224                             mHandlerCaller.sendMessageDelayed(msg,
225                                     CHECK_ALL_PRINTJOBS_HANDLED_DELAY);
226                         }
227                     }
228                 } break;
229
230                 case MSG_ON_PRINT_JOB_QUEUED: {
231                     PrintJobInfo printJob = (PrintJobInfo) message.obj;
232                     if (mClient != null) {
233                         try {
234                             mClient.onPrintJobQueued(printJob);
235                         } catch (RemoteException re) {
236                             Slog.e(LOG_TAG, "Error notify for a queued print job.", re);
237                         }
238                     }
239                 } break;
240
241                 case MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED: {
242                     ComponentName service = (ComponentName) message.obj;
243                     if (mClient != null) {
244                         try {
245                             mClient.onAllPrintJobsForServiceHandled(service);
246                         } catch (RemoteException re) {
247                             Slog.e(LOG_TAG, "Error notify for all print jobs per service"
248                                     + " handled.", re);
249                         }
250                     }
251                 } break;
252
253                 case MSG_ON_ALL_PRINT_JOBS_HANDLED: {
254                     if (mClient != null) {
255                         try {
256                             mClient.onAllPrintJobsHandled();
257                         } catch (RemoteException re) {
258                             Slog.e(LOG_TAG, "Error notify for all print job handled.", re);
259                         }
260                     }
261                 } break;
262
263                 case MSG_CHECK_ALL_PRINTJOBS_HANDLED: {
264                     checkAllPrintJobsHandled();
265                 } break;
266
267                 case MSG_ON_PRINT_JOB_STATE_CHANGED: {
268                     if (mClient != null) {
269                         PrintJobInfo printJob = (PrintJobInfo) message.obj;
270                         try {
271                             mClient.onPrintJobStateChanged(printJob);
272                         } catch (RemoteException re) {
273                             Slog.e(LOG_TAG, "Error notify for print job state change.", re);
274                         }
275                     }
276                 } break;
277             }
278         }
279     }
280
281     public List<PrintJobInfo> getPrintJobInfos(ComponentName componentName,
282             int state, int appId) {
283         List<PrintJobInfo> foundPrintJobs = null;
284         synchronized (mLock) {
285             final int printJobCount = mPrintJobs.size();
286             for (int i = 0; i < printJobCount; i++) {
287                 PrintJobInfo printJob = mPrintJobs.get(i);
288                 PrinterId printerId = printJob.getPrinterId();
289                 final boolean sameComponent = (componentName == null
290                         || (printerId != null
291                         && componentName.equals(printerId.getServiceName())));
292                 final boolean sameAppId = appId == PrintManager.APP_ID_ANY
293                         || printJob.getAppId() == appId;
294                 final boolean sameState = (state == printJob.getState())
295                         || (state == PrintJobInfo.STATE_ANY)
296                         || (state == PrintJobInfo.STATE_ANY_VISIBLE_TO_CLIENTS
297                             && isStateVisibleToUser(printJob.getState()))
298                         || (state == PrintJobInfo.STATE_ANY_ACTIVE
299                             && isActiveState(printJob.getState()))
300                         || (state == PrintJobInfo.STATE_ANY_SCHEDULED
301                             && isScheduledState(printJob.getState()));
302                 if (sameComponent && sameAppId && sameState) {
303                     if (foundPrintJobs == null) {
304                         foundPrintJobs = new ArrayList<>();
305                     }
306                     foundPrintJobs.add(printJob);
307                 }
308             }
309         }
310         return foundPrintJobs;
311     }
312
313     private boolean isStateVisibleToUser(int state) {
314         return (isActiveState(state) && (state == PrintJobInfo.STATE_FAILED
315                 || state == PrintJobInfo.STATE_COMPLETED || state == PrintJobInfo.STATE_CANCELED
316                 || state == PrintJobInfo.STATE_BLOCKED));
317     }
318
319     public PrintJobInfo getPrintJobInfo(PrintJobId printJobId, int appId) {
320         synchronized (mLock) {
321             final int printJobCount = mPrintJobs.size();
322             for (int i = 0; i < printJobCount; i++) {
323                 PrintJobInfo printJob = mPrintJobs.get(i);
324                 if (printJob.getId().equals(printJobId)
325                         && (appId == PrintManager.APP_ID_ANY
326                         || appId == printJob.getAppId())) {
327                     return printJob;
328                 }
329             }
330             return null;
331         }
332     }
333
334     public void createPrintJob(PrintJobInfo printJob) {
335         synchronized (mLock) {
336             addPrintJobLocked(printJob);
337             setPrintJobState(printJob.getId(), PrintJobInfo.STATE_CREATED, null);
338
339             Message message = mHandlerCaller.obtainMessageO(
340                     HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED,
341                     printJob);
342             mHandlerCaller.executeOrSendMessage(message);
343         }
344     }
345
346     private void handleReadPrintJobsLocked() {
347         // Make a map with the files for a print job since we may have
348         // to delete some. One example of getting orphan files if the
349         // spooler crashes while constructing a print job. We do not
350         // persist partially populated print jobs under construction to
351         // avoid special handling for various attributes missing.
352         ArrayMap<PrintJobId, File> fileForJobMap = null;
353         File[] files = getFilesDir().listFiles();
354         if (files != null) {
355             final int fileCount = files.length;
356             for (int i = 0; i < fileCount; i++) {
357                 File file = files[i];
358                 if (file.isFile() && file.getName().startsWith(PRINT_JOB_FILE_PREFIX)) {
359                     if (fileForJobMap == null) {
360                         fileForJobMap = new ArrayMap<PrintJobId, File>();
361                     }
362                     String printJobIdString = file.getName().substring(
363                             PRINT_JOB_FILE_PREFIX.length(),
364                             file.getName().indexOf('.'));
365                     PrintJobId printJobId = PrintJobId.unflattenFromString(
366                             printJobIdString);
367                     fileForJobMap.put(printJobId, file);
368                 }
369             }
370         }
371
372         final int printJobCount = mPrintJobs.size();
373         for (int i = 0; i < printJobCount; i++) {
374             PrintJobInfo printJob = mPrintJobs.get(i);
375
376             // We want to have only the orphan files at the end.
377             if (fileForJobMap != null) {
378                 fileForJobMap.remove(printJob.getId());
379             }
380
381             switch (printJob.getState()) {
382                 case PrintJobInfo.STATE_QUEUED:
383                 case PrintJobInfo.STATE_STARTED:
384                 case PrintJobInfo.STATE_BLOCKED: {
385                     // We have a print job that was queued or started or blocked in
386                     // the past but the device battery died or a crash occurred. In
387                     // this case we assume the print job failed and let the user
388                     // decide whether to restart the job or just cancel it.
389                     setPrintJobState(printJob.getId(), PrintJobInfo.STATE_FAILED,
390                             getString(R.string.no_connection_to_printer));
391                 } break;
392             }
393         }
394
395         if (!mPrintJobs.isEmpty()) {
396             // Update the notification.
397             mNotificationController.onUpdateNotifications(mPrintJobs);
398         }
399
400         // Delete the orphan files.
401         if (fileForJobMap != null) {
402             final int orphanFileCount = fileForJobMap.size();
403             for (int i = 0; i < orphanFileCount; i++) {
404                 File file = fileForJobMap.valueAt(i);
405                 file.delete();
406             }
407         }
408     }
409
410     public void checkAllPrintJobsHandled() {
411         synchronized (mLock) {
412             if (!hasActivePrintJobsLocked()) {
413                 notifyOnAllPrintJobsHandled();
414             }
415         }
416     }
417
418     public void writePrintJobData(final ParcelFileDescriptor fd, final PrintJobId printJobId) {
419         final PrintJobInfo printJob;
420         synchronized (mLock) {
421             printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
422         }
423         new AsyncTask<Void, Void, Void>() {
424             @Override
425             protected Void doInBackground(Void... params) {
426                 FileInputStream in = null;
427                 FileOutputStream out = null;
428                 try {
429                     if (printJob != null) {
430                         File file = generateFileForPrintJob(PrintSpoolerService.this, printJobId);
431                         in = new FileInputStream(file);
432                         out = new FileOutputStream(fd.getFileDescriptor());
433                     }
434                     final byte[] buffer = new byte[8192];
435                     while (true) {
436                         final int readByteCount = in.read(buffer);
437                         if (readByteCount < 0) {
438                             return null;
439                         }
440                         out.write(buffer, 0, readByteCount);
441                     }
442                 } catch (FileNotFoundException fnfe) {
443                     Log.e(LOG_TAG, "Error writing print job data!", fnfe);
444                 } catch (IOException ioe) {
445                     Log.e(LOG_TAG, "Error writing print job data!", ioe);
446                 } finally {
447                     IoUtils.closeQuietly(in);
448                     IoUtils.closeQuietly(out);
449                     IoUtils.closeQuietly(fd);
450                 }
451                 Log.i(LOG_TAG, "[END WRITE]");
452                 return null;
453             }
454         }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
455     }
456
457     public static File generateFileForPrintJob(Context context, PrintJobId printJobId) {
458         return new File(context.getFilesDir(), PRINT_JOB_FILE_PREFIX
459                 + printJobId.flattenToString() + "." + PRINT_FILE_EXTENSION);
460     }
461
462     private void addPrintJobLocked(PrintJobInfo printJob) {
463         mPrintJobs.add(printJob);
464         if (DEBUG_PRINT_JOB_LIFECYCLE) {
465             Slog.i(LOG_TAG, "[ADD] " + printJob);
466         }
467     }
468
469     private void removeObsoletePrintJobs() {
470         synchronized (mLock) {
471             boolean persistState = false;
472             final int printJobCount = mPrintJobs.size();
473             for (int i = printJobCount - 1; i >= 0; i--) {
474                 PrintJobInfo printJob = mPrintJobs.get(i);
475                 if (isObsoleteState(printJob.getState())) {
476                     mPrintJobs.remove(i);
477                     if (DEBUG_PRINT_JOB_LIFECYCLE) {
478                         Slog.i(LOG_TAG, "[REMOVE] " + printJob.getId().flattenToString());
479                     }
480                     removePrintJobFileLocked(printJob.getId());
481                     persistState = true;
482                 }
483             }
484             if (persistState) {
485                 mPersistanceManager.writeStateLocked();
486             }
487         }
488     }
489
490     private void removePrintJobFileLocked(PrintJobId printJobId) {
491         File file = generateFileForPrintJob(PrintSpoolerService.this, printJobId);
492         if (file.exists()) {
493             file.delete();
494             if (DEBUG_PRINT_JOB_LIFECYCLE) {
495                 Slog.i(LOG_TAG, "[REMOVE FILE FOR] " + printJobId);
496             }
497         }
498     }
499
500     public boolean setPrintJobState(PrintJobId printJobId, int state, String error) {
501         boolean success = false;
502
503         synchronized (mLock) {
504             PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
505             if (printJob != null) {
506                 final int oldState = printJob.getState();
507                 if (oldState == state) {
508                     return false;
509                 }
510
511                 success = true;
512
513                 printJob.setState(state);
514                 printJob.setStatus(error);
515                 printJob.setCancelling(false);
516
517                 if (DEBUG_PRINT_JOB_LIFECYCLE) {
518                     Slog.i(LOG_TAG, "[STATE CHANGED] " + printJob);
519                 }
520
521                 MetricsLogger.histogram(this, "print_job_state", state);
522                 switch (state) {
523                     case PrintJobInfo.STATE_COMPLETED:
524                     case PrintJobInfo.STATE_CANCELED:
525                         mPrintJobs.remove(printJob);
526                         removePrintJobFileLocked(printJob.getId());
527                         // $fall-through$
528
529                     case PrintJobInfo.STATE_FAILED: {
530                         PrinterId printerId = printJob.getPrinterId();
531                         if (printerId != null) {
532                             ComponentName service = printerId.getServiceName();
533                             if (!hasActivePrintJobsForServiceLocked(service)) {
534                                 sendOnAllPrintJobsForServiceHandled(service);
535                             }
536                         }
537                     } break;
538
539                     case PrintJobInfo.STATE_QUEUED: {
540                         sendOnPrintJobQueued(new PrintJobInfo(printJob));
541                     }  break;
542                 }
543
544                 if (shouldPersistPrintJob(printJob)) {
545                     mPersistanceManager.writeStateLocked();
546                 }
547
548                 if (!hasActivePrintJobsLocked()) {
549                     notifyOnAllPrintJobsHandled();
550                 }
551
552                 Message message = mHandlerCaller.obtainMessageO(
553                         HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED,
554                         printJob);
555                 mHandlerCaller.executeOrSendMessage(message);
556
557                 mNotificationController.onUpdateNotifications(mPrintJobs);
558             }
559         }
560
561         return success;
562     }
563
564     /**
565      * Set the progress for a print job.
566      *
567      * @param printJobId ID of the print job to update
568      * @param progress the new progress
569      */
570     public void setProgress(@NonNull PrintJobId printJobId,
571             @FloatRange(from=0.0, to=1.0) float progress) {
572         synchronized (mLock) {
573             getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY).setProgress(progress);
574
575             mNotificationController.onUpdateNotifications(mPrintJobs);
576         }
577     }
578
579     /**
580      * Set the status for a print job.
581      *
582      * @param printJobId ID of the print job to update
583      * @param status the new status
584      */
585     public void setStatus(@NonNull PrintJobId printJobId, @Nullable CharSequence status) {
586         synchronized (mLock) {
587             getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY).setStatus(status);
588
589             mNotificationController.onUpdateNotifications(mPrintJobs);
590         }
591     }
592
593     /**
594      * Set the status for a print job.
595      *
596      * @param printJobId ID of the print job to update
597      * @param status the new status as a string resource
598      * @param appPackageName app package the resource belongs to
599      */
600     public void setStatus(@NonNull PrintJobId printJobId, @StringRes int status,
601             @Nullable CharSequence appPackageName) {
602         synchronized (mLock) {
603             getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY).setStatus(status, appPackageName);
604
605             mNotificationController.onUpdateNotifications(mPrintJobs);
606         }
607     }
608
609     public boolean hasActivePrintJobsLocked() {
610         final int printJobCount = mPrintJobs.size();
611         for (int i = 0; i < printJobCount; i++) {
612             PrintJobInfo printJob = mPrintJobs.get(i);
613             if (isActiveState(printJob.getState())) {
614                 return true;
615             }
616         }
617         return false;
618     }
619
620     public boolean hasActivePrintJobsForServiceLocked(ComponentName service) {
621         final int printJobCount = mPrintJobs.size();
622         for (int i = 0; i < printJobCount; i++) {
623             PrintJobInfo printJob = mPrintJobs.get(i);
624             if (isActiveState(printJob.getState()) && printJob.getPrinterId() != null
625                     && printJob.getPrinterId().getServiceName().equals(service)) {
626                 return true;
627             }
628         }
629         return false;
630     }
631
632     private boolean isObsoleteState(int printJobState) {
633         return (isTerminalState(printJobState)
634                 || printJobState == PrintJobInfo.STATE_QUEUED);
635     }
636
637     private boolean isScheduledState(int printJobState) {
638         return printJobState == PrintJobInfo.STATE_QUEUED
639                 || printJobState == PrintJobInfo.STATE_STARTED
640                 || printJobState == PrintJobInfo.STATE_BLOCKED;
641     }
642
643     private boolean isActiveState(int printJobState) {
644         return printJobState == PrintJobInfo.STATE_CREATED
645                 || printJobState == PrintJobInfo.STATE_QUEUED
646                 || printJobState == PrintJobInfo.STATE_STARTED
647                 || printJobState == PrintJobInfo.STATE_BLOCKED;
648     }
649
650     private boolean isTerminalState(int printJobState) {
651         return printJobState == PrintJobInfo.STATE_COMPLETED
652                 || printJobState == PrintJobInfo.STATE_CANCELED;
653     }
654
655     public boolean setPrintJobTag(PrintJobId printJobId, String tag) {
656         synchronized (mLock) {
657             PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
658             if (printJob != null) {
659                 String printJobTag = printJob.getTag();
660                 if (printJobTag == null) {
661                     if (tag == null) {
662                         return false;
663                     }
664                 } else if (printJobTag.equals(tag)) {
665                     return false;
666                 }
667                 printJob.setTag(tag);
668                 if (shouldPersistPrintJob(printJob)) {
669                     mPersistanceManager.writeStateLocked();
670                 }
671                 return true;
672             }
673         }
674         return false;
675     }
676
677     public void setPrintJobCancelling(PrintJobId printJobId, boolean cancelling) {
678         synchronized (mLock) {
679             PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
680             if (printJob != null) {
681                 printJob.setCancelling(cancelling);
682                 if (shouldPersistPrintJob(printJob)) {
683                     mPersistanceManager.writeStateLocked();
684                 }
685                 mNotificationController.onUpdateNotifications(mPrintJobs);
686
687                 Message message = mHandlerCaller.obtainMessageO(
688                         HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED,
689                         printJob);
690                 mHandlerCaller.executeOrSendMessage(message);
691             }
692         }
693     }
694
695     public void updatePrintJobUserConfigurableOptionsNoPersistence(PrintJobInfo printJob) {
696         synchronized (mLock) {
697             final int printJobCount = mPrintJobs.size();
698             for (int i = 0; i < printJobCount; i++) {
699                 PrintJobInfo cachedPrintJob = mPrintJobs.get(i);
700                 if (cachedPrintJob.getId().equals(printJob.getId())) {
701                     cachedPrintJob.setPrinterId(printJob.getPrinterId());
702                     cachedPrintJob.setPrinterName(printJob.getPrinterName());
703                     cachedPrintJob.setCopies(printJob.getCopies());
704                     cachedPrintJob.setDocumentInfo(printJob.getDocumentInfo());
705                     cachedPrintJob.setPages(printJob.getPages());
706                     cachedPrintJob.setAttributes(printJob.getAttributes());
707                     cachedPrintJob.setAdvancedOptions(printJob.getAdvancedOptions());
708                     return;
709                 }
710             }
711             throw new IllegalArgumentException("No print job with id:" + printJob.getId());
712         }
713     }
714
715     private boolean shouldPersistPrintJob(PrintJobInfo printJob) {
716         return printJob.getState() >= PrintJobInfo.STATE_QUEUED;
717     }
718
719     private void notifyOnAllPrintJobsHandled() {
720         // This has to run on the tread that is persisting the current state
721         // since this call may result in the system unbinding from the spooler
722         // and as a result the spooler process may get killed before the write
723         // completes.
724         new AsyncTask<Void, Void, Void>() {
725             @Override
726             protected Void doInBackground(Void... params) {
727                 sendOnAllPrintJobsHandled();
728                 return null;
729             }
730         }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
731     }
732
733     /**
734      * Handle that a custom icon for a printer was loaded.
735      *
736      * @param printerId the id of the printer the icon belongs to
737      * @param icon the icon that was loaded
738      * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon()
739      */
740     public void onCustomPrinterIconLoaded(PrinterId printerId, Icon icon) {
741         mCustomIconCache.onCustomPrinterIconLoaded(printerId, icon);
742     }
743
744     /**
745      * Get the custom icon for a printer. If the icon is not cached, the icon is
746      * requested asynchronously. Once it is available the printer is updated.
747      *
748      * @param printerId the id of the printer the icon should be loaded for
749      * @return the custom icon to be used for the printer or null if the icon is
750      *         not yet available
751      * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon()
752      */
753     public Icon getCustomPrinterIcon(PrinterId printerId) {
754         return mCustomIconCache.getIcon(printerId);
755     }
756
757     /**
758      * Clear the custom printer icon cache.
759      */
760     public void clearCustomPrinterIconCache() {
761         mCustomIconCache.clear();
762     }
763
764     private final class PersistenceManager {
765         private static final String PERSIST_FILE_NAME = "print_spooler_state.xml";
766
767         private static final String TAG_SPOOLER = "spooler";
768         private static final String TAG_JOB = "job";
769
770         private static final String TAG_PRINTER_ID = "printerId";
771         private static final String TAG_PAGE_RANGE = "pageRange";
772         private static final String TAG_ATTRIBUTES = "attributes";
773         private static final String TAG_DOCUMENT_INFO = "documentInfo";
774
775         private static final String ATTR_ID = "id";
776         private static final String ATTR_LABEL = "label";
777         private static final String ATTR_LABEL_RES_ID = "labelResId";
778         private static final String ATTR_PACKAGE_NAME = "packageName";
779         private static final String ATTR_STATE = "state";
780         private static final String ATTR_APP_ID = "appId";
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         private static final String ATTR_STATUS = "status";
787         private static final String ATTR_PROGRESS = "progress";
788         private static final String ATTR_CANCELLING = "cancelling";
789
790         private static final String TAG_ADVANCED_OPTIONS = "advancedOptions";
791         private static final String TAG_ADVANCED_OPTION = "advancedOption";
792         private static final String ATTR_KEY = "key";
793         private static final String ATTR_TYPE = "type";
794         private static final String ATTR_VALUE = "value";
795         private static final String TYPE_STRING = "string";
796         private static final String TYPE_INT = "int";
797
798         private static final String TAG_MEDIA_SIZE = "mediaSize";
799         private static final String TAG_RESOLUTION = "resolution";
800         private static final String TAG_MARGINS = "margins";
801
802         private static final String ATTR_COLOR_MODE = "colorMode";
803         private static final String ATTR_DUPLEX_MODE = "duplexMode";
804
805         private static final String ATTR_LOCAL_ID = "localId";
806         private static final String ATTR_SERVICE_NAME = "serviceName";
807
808         private static final String ATTR_WIDTH_MILS = "widthMils";
809         private static final String ATTR_HEIGHT_MILS = "heightMils";
810
811         private static final String ATTR_HORIZONTAL_DPI = "horizontalDip";
812         private static final String ATTR_VERTICAL_DPI = "verticalDpi";
813
814         private static final String ATTR_LEFT_MILS = "leftMils";
815         private static final String ATTR_TOP_MILS = "topMils";
816         private static final String ATTR_RIGHT_MILS = "rightMils";
817         private static final String ATTR_BOTTOM_MILS = "bottomMils";
818
819         private static final String ATTR_START = "start";
820         private static final String ATTR_END = "end";
821
822         private static final String ATTR_NAME = "name";
823         private static final String ATTR_PAGE_COUNT = "pageCount";
824         private static final String ATTR_CONTENT_TYPE = "contentType";
825         private static final String ATTR_DATA_SIZE = "dataSize";
826
827         private final AtomicFile mStatePersistFile;
828
829         private boolean mWriteStateScheduled;
830
831         private PersistenceManager() {
832             mStatePersistFile = new AtomicFile(new File(getFilesDir(),
833                     PERSIST_FILE_NAME));
834         }
835
836         public void writeStateLocked() {
837             if (!PERSISTENCE_MANAGER_ENABLED) {
838                 return;
839             }
840             if (mWriteStateScheduled) {
841                 return;
842             }
843             mWriteStateScheduled = true;
844             new AsyncTask<Void, Void, Void>() {
845                 @Override
846                 protected Void doInBackground(Void... params) {
847                     synchronized (mLock) {
848                         mWriteStateScheduled = false;
849                         doWriteStateLocked();
850                     }
851                     return null;
852                 }
853             }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
854         }
855
856         private void doWriteStateLocked() {
857             if (DEBUG_PERSISTENCE) {
858                 Log.i(LOG_TAG, "[PERSIST START]");
859             }
860             FileOutputStream out = null;
861             try {
862                 out = mStatePersistFile.startWrite();
863
864                 XmlSerializer serializer = new FastXmlSerializer();
865                 serializer.setOutput(out, StandardCharsets.UTF_8.name());
866                 serializer.startDocument(null, true);
867                 serializer.startTag(null, TAG_SPOOLER);
868
869                 List<PrintJobInfo> printJobs = mPrintJobs;
870
871                 final int printJobCount = printJobs.size();
872                 for (int j = 0; j < printJobCount; j++) {
873                     PrintJobInfo printJob = printJobs.get(j);
874
875                     if (!shouldPersistPrintJob(printJob)) {
876                         continue;
877                     }
878
879                     serializer.startTag(null, TAG_JOB);
880
881                     serializer.attribute(null, ATTR_ID, printJob.getId().flattenToString());
882                     serializer.attribute(null, ATTR_LABEL, printJob.getLabel().toString());
883                     serializer.attribute(null, ATTR_STATE, String.valueOf(printJob.getState()));
884                     serializer.attribute(null, ATTR_APP_ID, String.valueOf(printJob.getAppId()));
885                     String tag = printJob.getTag();
886                     if (tag != null) {
887                         serializer.attribute(null, ATTR_TAG, tag);
888                     }
889                     serializer.attribute(null, ATTR_CREATION_TIME, String.valueOf(
890                             printJob.getCreationTime()));
891                     serializer.attribute(null, ATTR_COPIES, String.valueOf(printJob.getCopies()));
892                     String printerName = printJob.getPrinterName();
893                     if (!TextUtils.isEmpty(printerName)) {
894                         serializer.attribute(null, ATTR_PRINTER_NAME, printerName);
895                     }
896                     serializer.attribute(null, ATTR_CANCELLING, String.valueOf(
897                             printJob.isCancelling()));
898
899                     float progress = printJob.getProgress();
900                     if (progress != Float.NaN) {
901                         serializer.attribute(null, ATTR_PROGRESS, String.valueOf(progress));
902                     }
903
904                     CharSequence status = printJob.getStatus(getPackageManager());
905                     if (!TextUtils.isEmpty(status)) {
906                         serializer.attribute(null, ATTR_STATUS, status.toString());
907                     }
908
909                     PrinterId printerId = printJob.getPrinterId();
910                     if (printerId != null) {
911                         serializer.startTag(null, TAG_PRINTER_ID);
912                         serializer.attribute(null, ATTR_LOCAL_ID, printerId.getLocalId());
913                         serializer.attribute(null, ATTR_SERVICE_NAME, printerId.getServiceName()
914                                 .flattenToString());
915                         serializer.endTag(null, TAG_PRINTER_ID);
916                     }
917
918                     PageRange[] pages = printJob.getPages();
919                     if (pages != null) {
920                         for (int i = 0; i < pages.length; i++) {
921                             serializer.startTag(null, TAG_PAGE_RANGE);
922                             serializer.attribute(null, ATTR_START, String.valueOf(
923                                     pages[i].getStart()));
924                             serializer.attribute(null, ATTR_END, String.valueOf(
925                                     pages[i].getEnd()));
926                             serializer.endTag(null, TAG_PAGE_RANGE);
927                         }
928                     }
929
930                     PrintAttributes attributes = printJob.getAttributes();
931                     if (attributes != null) {
932                         serializer.startTag(null, TAG_ATTRIBUTES);
933
934                         final int colorMode = attributes.getColorMode();
935                         serializer.attribute(null, ATTR_COLOR_MODE,
936                                 String.valueOf(colorMode));
937
938                         final int duplexMode = attributes.getDuplexMode();
939                         serializer.attribute(null, ATTR_DUPLEX_MODE,
940                                 String.valueOf(duplexMode));
941
942                         MediaSize mediaSize = attributes.getMediaSize();
943                         if (mediaSize != null) {
944                             serializer.startTag(null, TAG_MEDIA_SIZE);
945                             serializer.attribute(null, ATTR_ID, mediaSize.getId());
946                             serializer.attribute(null, ATTR_WIDTH_MILS, String.valueOf(
947                                     mediaSize.getWidthMils()));
948                             serializer.attribute(null, ATTR_HEIGHT_MILS, String.valueOf(
949                                     mediaSize.getHeightMils()));
950                             // We prefer to store only the package name and
951                             // resource id and fallback to the label.
952                             if (!TextUtils.isEmpty(mediaSize.mPackageName)
953                                     && mediaSize.mLabelResId > 0) {
954                                 serializer.attribute(null, ATTR_PACKAGE_NAME,
955                                         mediaSize.mPackageName);
956                                 serializer.attribute(null, ATTR_LABEL_RES_ID,
957                                         String.valueOf(mediaSize.mLabelResId));
958                             } else {
959                                 serializer.attribute(null, ATTR_LABEL,
960                                         mediaSize.getLabel(getPackageManager()));
961                             }
962                             serializer.endTag(null, TAG_MEDIA_SIZE);
963                         }
964
965                         Resolution resolution = attributes.getResolution();
966                         if (resolution != null) {
967                             serializer.startTag(null, TAG_RESOLUTION);
968                             serializer.attribute(null, ATTR_ID, resolution.getId());
969                             serializer.attribute(null, ATTR_HORIZONTAL_DPI, String.valueOf(
970                                     resolution.getHorizontalDpi()));
971                             serializer.attribute(null, ATTR_VERTICAL_DPI, String.valueOf(
972                                     resolution.getVerticalDpi()));
973                             serializer.attribute(null, ATTR_LABEL,
974                                     resolution.getLabel());
975                             serializer.endTag(null, TAG_RESOLUTION);
976                         }
977
978                         Margins margins = attributes.getMinMargins();
979                         if (margins != null) {
980                             serializer.startTag(null, TAG_MARGINS);
981                             serializer.attribute(null, ATTR_LEFT_MILS, String.valueOf(
982                                     margins.getLeftMils()));
983                             serializer.attribute(null, ATTR_TOP_MILS, String.valueOf(
984                                     margins.getTopMils()));
985                             serializer.attribute(null, ATTR_RIGHT_MILS, String.valueOf(
986                                     margins.getRightMils()));
987                             serializer.attribute(null, ATTR_BOTTOM_MILS, String.valueOf(
988                                     margins.getBottomMils()));
989                             serializer.endTag(null, TAG_MARGINS);
990                         }
991
992                         serializer.endTag(null, TAG_ATTRIBUTES);
993                     }
994
995                     PrintDocumentInfo documentInfo = printJob.getDocumentInfo();
996                     if (documentInfo != null) {
997                         serializer.startTag(null, TAG_DOCUMENT_INFO);
998                         serializer.attribute(null, ATTR_NAME, documentInfo.getName());
999                         serializer.attribute(null, ATTR_CONTENT_TYPE, String.valueOf(
1000                                 documentInfo.getContentType()));
1001                         serializer.attribute(null, ATTR_PAGE_COUNT, String.valueOf(
1002                                 documentInfo.getPageCount()));
1003                         serializer.attribute(null, ATTR_DATA_SIZE, String.valueOf(
1004                                 documentInfo.getDataSize()));
1005                         serializer.endTag(null, TAG_DOCUMENT_INFO);
1006                     }
1007
1008                     Bundle advancedOptions = printJob.getAdvancedOptions();
1009                     if (advancedOptions != null) {
1010                         serializer.startTag(null, TAG_ADVANCED_OPTIONS);
1011                         for (String key : advancedOptions.keySet()) {
1012                             Object value = advancedOptions.get(key);
1013                             if (value instanceof String) {
1014                                 String stringValue = (String) value;
1015                                 serializer.startTag(null, TAG_ADVANCED_OPTION);
1016                                 serializer.attribute(null, ATTR_KEY, key);
1017                                 serializer.attribute(null, ATTR_TYPE, TYPE_STRING);
1018                                 serializer.attribute(null, ATTR_VALUE, stringValue);
1019                                 serializer.endTag(null, TAG_ADVANCED_OPTION);
1020                             } else if (value instanceof Integer) {
1021                                 String intValue = Integer.toString((Integer) value);
1022                                 serializer.startTag(null, TAG_ADVANCED_OPTION);
1023                                 serializer.attribute(null, ATTR_KEY, key);
1024                                 serializer.attribute(null, ATTR_TYPE, TYPE_INT);
1025                                 serializer.attribute(null, ATTR_VALUE, intValue);
1026                                 serializer.endTag(null, TAG_ADVANCED_OPTION);
1027                             }
1028                         }
1029                         serializer.endTag(null, TAG_ADVANCED_OPTIONS);
1030                     }
1031
1032                     serializer.endTag(null, TAG_JOB);
1033
1034                     if (DEBUG_PERSISTENCE) {
1035                         Log.i(LOG_TAG, "[PERSISTED] " + printJob);
1036                     }
1037                 }
1038
1039                 serializer.endTag(null, TAG_SPOOLER);
1040                 serializer.endDocument();
1041                 mStatePersistFile.finishWrite(out);
1042                 if (DEBUG_PERSISTENCE) {
1043                     Log.i(LOG_TAG, "[PERSIST END]");
1044                 }
1045             } catch (IOException e) {
1046                 Slog.w(LOG_TAG, "Failed to write state, restoring backup.", e);
1047                 mStatePersistFile.failWrite(out);
1048             } finally {
1049                 IoUtils.closeQuietly(out);
1050             }
1051         }
1052
1053         public void readStateLocked() {
1054             if (!PERSISTENCE_MANAGER_ENABLED) {
1055                 return;
1056             }
1057             FileInputStream in = null;
1058             try {
1059                 in = mStatePersistFile.openRead();
1060             } catch (FileNotFoundException e) {
1061                 if (DEBUG_PERSISTENCE) {
1062                     Log.d(LOG_TAG, "No existing print spooler state.");
1063                 }
1064                 return;
1065             }
1066             try {
1067                 XmlPullParser parser = Xml.newPullParser();
1068                 parser.setInput(in, StandardCharsets.UTF_8.name());
1069                 parseState(parser);
1070             } catch (IllegalStateException ise) {
1071                 Slog.w(LOG_TAG, "Failed parsing ", ise);
1072             } catch (NullPointerException npe) {
1073                 Slog.w(LOG_TAG, "Failed parsing ", npe);
1074             } catch (NumberFormatException nfe) {
1075                 Slog.w(LOG_TAG, "Failed parsing ", nfe);
1076             } catch (XmlPullParserException xppe) {
1077                 Slog.w(LOG_TAG, "Failed parsing ", xppe);
1078             } catch (IOException ioe) {
1079                 Slog.w(LOG_TAG, "Failed parsing ", ioe);
1080             } catch (IndexOutOfBoundsException iobe) {
1081                 Slog.w(LOG_TAG, "Failed parsing ", iobe);
1082             } finally {
1083                 IoUtils.closeQuietly(in);
1084             }
1085         }
1086
1087         private void parseState(XmlPullParser parser)
1088                 throws IOException, XmlPullParserException {
1089             parser.next();
1090             skipEmptyTextTags(parser);
1091             expect(parser, XmlPullParser.START_TAG, TAG_SPOOLER);
1092             parser.next();
1093
1094             while (parsePrintJob(parser)) {
1095                 parser.next();
1096             }
1097
1098             skipEmptyTextTags(parser);
1099             expect(parser, XmlPullParser.END_TAG, TAG_SPOOLER);
1100         }
1101
1102         private boolean parsePrintJob(XmlPullParser parser)
1103                 throws IOException, XmlPullParserException {
1104             skipEmptyTextTags(parser);
1105             if (!accept(parser, XmlPullParser.START_TAG, TAG_JOB)) {
1106                 return false;
1107             }
1108
1109             PrintJobInfo printJob = new PrintJobInfo();
1110
1111             PrintJobId printJobId = PrintJobId.unflattenFromString(
1112                     parser.getAttributeValue(null, ATTR_ID));
1113             printJob.setId(printJobId);
1114             String label = parser.getAttributeValue(null, ATTR_LABEL);
1115             printJob.setLabel(label);
1116             final int state = Integer.parseInt(parser.getAttributeValue(null, ATTR_STATE));
1117             printJob.setState(state);
1118             final int appId = Integer.parseInt(parser.getAttributeValue(null, ATTR_APP_ID));
1119             printJob.setAppId(appId);
1120             String tag = parser.getAttributeValue(null, ATTR_TAG);
1121             printJob.setTag(tag);
1122             String creationTime = parser.getAttributeValue(null, ATTR_CREATION_TIME);
1123             printJob.setCreationTime(Long.parseLong(creationTime));
1124             String copies = parser.getAttributeValue(null, ATTR_COPIES);
1125             printJob.setCopies(Integer.parseInt(copies));
1126             String printerName = parser.getAttributeValue(null, ATTR_PRINTER_NAME);
1127             printJob.setPrinterName(printerName);
1128
1129             String progressString = parser.getAttributeValue(null, ATTR_PROGRESS);
1130             if (progressString != null) {
1131                 float progress = Float.parseFloat(progressString);
1132
1133                 if (progress != -1) {
1134                     printJob.setProgress(progress);
1135                 }
1136             }
1137
1138             CharSequence status = parser.getAttributeValue(null, ATTR_STATUS);
1139             printJob.setStatus(status);
1140
1141             // stateReason is deprecated, but might be used by old print jobs
1142             String stateReason = parser.getAttributeValue(null, ATTR_STATE_REASON);
1143             if (stateReason != null) {
1144                 printJob.setStatus(stateReason);
1145             }
1146
1147             String cancelling = parser.getAttributeValue(null, ATTR_CANCELLING);
1148             printJob.setCancelling(!TextUtils.isEmpty(cancelling)
1149                     ? Boolean.parseBoolean(cancelling) : false);
1150
1151             parser.next();
1152
1153             skipEmptyTextTags(parser);
1154             if (accept(parser, XmlPullParser.START_TAG, TAG_PRINTER_ID)) {
1155                 String localId = parser.getAttributeValue(null, ATTR_LOCAL_ID);
1156                 ComponentName service = ComponentName.unflattenFromString(parser.getAttributeValue(
1157                         null, ATTR_SERVICE_NAME));
1158                 printJob.setPrinterId(new PrinterId(service, localId));
1159                 parser.next();
1160                 skipEmptyTextTags(parser);
1161                 expect(parser, XmlPullParser.END_TAG, TAG_PRINTER_ID);
1162                 parser.next();
1163             }
1164
1165             skipEmptyTextTags(parser);
1166             List<PageRange> pageRanges = null;
1167             while (accept(parser, XmlPullParser.START_TAG, TAG_PAGE_RANGE)) {
1168                 final int start = Integer.parseInt(parser.getAttributeValue(null, ATTR_START));
1169                 final int end = Integer.parseInt(parser.getAttributeValue(null, ATTR_END));
1170                 PageRange pageRange = new PageRange(start, end);
1171                 if (pageRanges == null) {
1172                     pageRanges = new ArrayList<PageRange>();
1173                 }
1174                 pageRanges.add(pageRange);
1175                 parser.next();
1176                 skipEmptyTextTags(parser);
1177                 expect(parser, XmlPullParser.END_TAG, TAG_PAGE_RANGE);
1178                 parser.next();
1179                 skipEmptyTextTags(parser);
1180             }
1181             if (pageRanges != null) {
1182                 PageRange[] pageRangesArray = new PageRange[pageRanges.size()];
1183                 pageRanges.toArray(pageRangesArray);
1184                 printJob.setPages(pageRangesArray);
1185             }
1186
1187             skipEmptyTextTags(parser);
1188             if (accept(parser, XmlPullParser.START_TAG, TAG_ATTRIBUTES)) {
1189
1190                 PrintAttributes.Builder builder = new PrintAttributes.Builder();
1191
1192                 String colorMode = parser.getAttributeValue(null, ATTR_COLOR_MODE);
1193                 builder.setColorMode(Integer.parseInt(colorMode));
1194
1195                 String duplexMode = parser.getAttributeValue(null, ATTR_DUPLEX_MODE);
1196                 // Duplex mode was added later, so null check is needed.
1197                 if (duplexMode != null) {
1198                     builder.setDuplexMode(Integer.parseInt(duplexMode));
1199                 }
1200
1201                 parser.next();
1202
1203                 skipEmptyTextTags(parser);
1204                 if (accept(parser, XmlPullParser.START_TAG, TAG_MEDIA_SIZE)) {
1205                     String id = parser.getAttributeValue(null, ATTR_ID);
1206                     label = parser.getAttributeValue(null, ATTR_LABEL);
1207                     final int widthMils = Integer.parseInt(parser.getAttributeValue(null,
1208                             ATTR_WIDTH_MILS));
1209                     final int heightMils = Integer.parseInt(parser.getAttributeValue(null,
1210                             ATTR_HEIGHT_MILS));
1211                     String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
1212                     String labelResIdString = parser.getAttributeValue(null, ATTR_LABEL_RES_ID);
1213                     final int labelResId = (labelResIdString != null)
1214                             ? Integer.parseInt(labelResIdString) : 0;
1215                     label = parser.getAttributeValue(null, ATTR_LABEL);
1216                     MediaSize mediaSize = new MediaSize(id, label, packageName,
1217                                 widthMils, heightMils, labelResId);
1218                     builder.setMediaSize(mediaSize);
1219                     parser.next();
1220                     skipEmptyTextTags(parser);
1221                     expect(parser, XmlPullParser.END_TAG, TAG_MEDIA_SIZE);
1222                     parser.next();
1223                 }
1224
1225                 skipEmptyTextTags(parser);
1226                 if (accept(parser, XmlPullParser.START_TAG, TAG_RESOLUTION)) {
1227                     String id = parser.getAttributeValue(null, ATTR_ID);
1228                     label = parser.getAttributeValue(null, ATTR_LABEL);
1229                     final int horizontalDpi = Integer.parseInt(parser.getAttributeValue(null,
1230                             ATTR_HORIZONTAL_DPI));
1231                     final int verticalDpi = Integer.parseInt(parser.getAttributeValue(null,
1232                             ATTR_VERTICAL_DPI));
1233                     Resolution resolution = new Resolution(id, label, horizontalDpi, verticalDpi);
1234                     builder.setResolution(resolution);
1235                     parser.next();
1236                     skipEmptyTextTags(parser);
1237                     expect(parser, XmlPullParser.END_TAG, TAG_RESOLUTION);
1238                     parser.next();
1239                 }
1240
1241                 skipEmptyTextTags(parser);
1242                 if (accept(parser, XmlPullParser.START_TAG, TAG_MARGINS)) {
1243                     final int leftMils = Integer.parseInt(parser.getAttributeValue(null,
1244                             ATTR_LEFT_MILS));
1245                     final int topMils = Integer.parseInt(parser.getAttributeValue(null,
1246                             ATTR_TOP_MILS));
1247                     final int rightMils = Integer.parseInt(parser.getAttributeValue(null,
1248                             ATTR_RIGHT_MILS));
1249                     final int bottomMils = Integer.parseInt(parser.getAttributeValue(null,
1250                             ATTR_BOTTOM_MILS));
1251                     Margins margins = new Margins(leftMils, topMils, rightMils, bottomMils);
1252                     builder.setMinMargins(margins);
1253                     parser.next();
1254                     skipEmptyTextTags(parser);
1255                     expect(parser, XmlPullParser.END_TAG, TAG_MARGINS);
1256                     parser.next();
1257                 }
1258
1259                 printJob.setAttributes(builder.build());
1260
1261                 skipEmptyTextTags(parser);
1262                 expect(parser, XmlPullParser.END_TAG, TAG_ATTRIBUTES);
1263                 parser.next();
1264             }
1265
1266             skipEmptyTextTags(parser);
1267             if (accept(parser, XmlPullParser.START_TAG, TAG_DOCUMENT_INFO)) {
1268                 String name = parser.getAttributeValue(null, ATTR_NAME);
1269                 final int pageCount = Integer.parseInt(parser.getAttributeValue(null,
1270                         ATTR_PAGE_COUNT));
1271                 final int contentType = Integer.parseInt(parser.getAttributeValue(null,
1272                         ATTR_CONTENT_TYPE));
1273                 final int dataSize = Integer.parseInt(parser.getAttributeValue(null,
1274                         ATTR_DATA_SIZE));
1275                 PrintDocumentInfo info = new PrintDocumentInfo.Builder(name)
1276                         .setPageCount(pageCount)
1277                         .setContentType(contentType).build();
1278                 printJob.setDocumentInfo(info);
1279                 info.setDataSize(dataSize);
1280                 parser.next();
1281                 skipEmptyTextTags(parser);
1282                 expect(parser, XmlPullParser.END_TAG, TAG_DOCUMENT_INFO);
1283                 parser.next();
1284             }
1285
1286             skipEmptyTextTags(parser);
1287             if (accept(parser, XmlPullParser.START_TAG, TAG_ADVANCED_OPTIONS)) {
1288                 parser.next();
1289                 skipEmptyTextTags(parser);
1290                 Bundle advancedOptions = new Bundle();
1291                 while (accept(parser, XmlPullParser.START_TAG, TAG_ADVANCED_OPTION)) {
1292                     String key = parser.getAttributeValue(null, ATTR_KEY);
1293                     String value = parser.getAttributeValue(null, ATTR_VALUE);
1294                     String type = parser.getAttributeValue(null, ATTR_TYPE);
1295                     if (TYPE_STRING.equals(type)) {
1296                         advancedOptions.putString(key, value);
1297                     } else if (TYPE_INT.equals(type)) {
1298                         advancedOptions.putInt(key, Integer.valueOf(value));
1299                     }
1300                     parser.next();
1301                     skipEmptyTextTags(parser);
1302                     expect(parser, XmlPullParser.END_TAG, TAG_ADVANCED_OPTION);
1303                     parser.next();
1304                     skipEmptyTextTags(parser);
1305                 }
1306                 printJob.setAdvancedOptions(advancedOptions);
1307                 skipEmptyTextTags(parser);
1308                 expect(parser, XmlPullParser.END_TAG, TAG_ADVANCED_OPTIONS);
1309                 parser.next();
1310             }
1311
1312             mPrintJobs.add(printJob);
1313
1314             if (DEBUG_PERSISTENCE) {
1315                 Log.i(LOG_TAG, "[RESTORED] " + printJob);
1316             }
1317
1318             skipEmptyTextTags(parser);
1319             expect(parser, XmlPullParser.END_TAG, TAG_JOB);
1320
1321             return true;
1322         }
1323
1324         private void expect(XmlPullParser parser, int type, String tag)
1325                 throws XmlPullParserException {
1326             if (!accept(parser, type, tag)) {
1327                 throw new XmlPullParserException("Exepected event: " + type
1328                         + " and tag: " + tag + " but got event: " + parser.getEventType()
1329                         + " and tag:" + parser.getName());
1330             }
1331         }
1332
1333         private void skipEmptyTextTags(XmlPullParser parser)
1334                 throws IOException, XmlPullParserException {
1335             while (accept(parser, XmlPullParser.TEXT, null)
1336                     && "\n".equals(parser.getText())) {
1337                 parser.next();
1338             }
1339         }
1340
1341         private boolean accept(XmlPullParser parser, int type, String tag)
1342                 throws XmlPullParserException {
1343             if (parser.getEventType() != type) {
1344                 return false;
1345             }
1346             if (tag != null) {
1347                 if (!tag.equals(parser.getName())) {
1348                     return false;
1349                 }
1350             } else if (parser.getName() != null) {
1351                 return false;
1352             }
1353             return true;
1354         }
1355     }
1356
1357     public final class PrintSpooler extends IPrintSpooler.Stub {
1358         @Override
1359         public void getPrintJobInfos(IPrintSpoolerCallbacks callback,
1360                 ComponentName componentName, int state, int appId, int sequence)
1361                 throws RemoteException {
1362             List<PrintJobInfo> printJobs = null;
1363             try {
1364                 printJobs = PrintSpoolerService.this.getPrintJobInfos(
1365                         componentName, state, appId);
1366             } finally {
1367                 callback.onGetPrintJobInfosResult(printJobs, sequence);
1368             }
1369         }
1370
1371         @Override
1372         public void getPrintJobInfo(PrintJobId printJobId, IPrintSpoolerCallbacks callback,
1373                 int appId, int sequence) throws RemoteException {
1374             PrintJobInfo printJob = null;
1375             try {
1376                 printJob = PrintSpoolerService.this.getPrintJobInfo(printJobId, appId);
1377             } finally {
1378                 callback.onGetPrintJobInfoResult(printJob, sequence);
1379             }
1380         }
1381
1382         @Override
1383         public void createPrintJob(PrintJobInfo printJob) {
1384             PrintSpoolerService.this.createPrintJob(printJob);
1385         }
1386
1387         @Override
1388         public void setPrintJobState(PrintJobId printJobId, int state, String error,
1389                 IPrintSpoolerCallbacks callback, int sequece) throws RemoteException {
1390             boolean success = false;
1391             try {
1392                 success = PrintSpoolerService.this.setPrintJobState(
1393                         printJobId, state, error);
1394             } finally {
1395                 callback.onSetPrintJobStateResult(success, sequece);
1396             }
1397         }
1398
1399         @Override
1400         public void setPrintJobTag(PrintJobId printJobId, String tag,
1401                 IPrintSpoolerCallbacks callback, int sequece) throws RemoteException {
1402             boolean success = false;
1403             try {
1404                 success = PrintSpoolerService.this.setPrintJobTag(printJobId, tag);
1405             } finally {
1406                 callback.onSetPrintJobTagResult(success, sequece);
1407             }
1408         }
1409
1410         @Override
1411         public void writePrintJobData(ParcelFileDescriptor fd, PrintJobId printJobId) {
1412             PrintSpoolerService.this.writePrintJobData(fd, printJobId);
1413         }
1414
1415         @Override
1416         public void setClient(IPrintSpoolerClient client) {
1417             Message message = mHandlerCaller.obtainMessageO(
1418                     HandlerCallerCallback.MSG_SET_CLIENT, client);
1419             mHandlerCaller.executeOrSendMessage(message);
1420         }
1421
1422         @Override
1423         public void removeObsoletePrintJobs() {
1424             PrintSpoolerService.this.removeObsoletePrintJobs();
1425         }
1426
1427         @Override
1428         protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
1429             PrintSpoolerService.this.dump(fd, writer, args);
1430         }
1431
1432         @Override
1433         public void setPrintJobCancelling(PrintJobId printJobId, boolean cancelling) {
1434             PrintSpoolerService.this.setPrintJobCancelling(printJobId, cancelling);
1435         }
1436
1437         @Override
1438         public void pruneApprovedPrintServices(List<ComponentName> servicesToKeep) {
1439             (new ApprovedPrintServices(PrintSpoolerService.this))
1440                     .pruneApprovedServices(servicesToKeep);
1441         }
1442
1443         @Override
1444         public void setProgress(@NonNull PrintJobId printJobId,
1445                 @FloatRange(from=0.0, to=1.0) float progress) throws RemoteException {
1446             PrintSpoolerService.this.setProgress(printJobId, progress);
1447         }
1448
1449         @Override
1450         public void setStatus(@NonNull PrintJobId printJobId,
1451                 @Nullable CharSequence status) throws RemoteException {
1452             PrintSpoolerService.this.setStatus(printJobId, status);
1453         }
1454
1455         @Override
1456         public void setStatusRes(@NonNull PrintJobId printJobId, @StringRes int status,
1457                 @NonNull CharSequence appPackageName) throws RemoteException {
1458             PrintSpoolerService.this.setStatus(printJobId, status, appPackageName);
1459         }
1460
1461
1462         public PrintSpoolerService getService() {
1463             return PrintSpoolerService.this;
1464         }
1465
1466         @Override
1467         public void onCustomPrinterIconLoaded(PrinterId printerId, Icon icon,
1468                 IPrintSpoolerCallbacks callbacks, int sequence)
1469                 throws RemoteException {
1470             try {
1471                 PrintSpoolerService.this.onCustomPrinterIconLoaded(printerId, icon);
1472             } finally {
1473                 callbacks.onCustomPrinterIconCached(sequence);
1474             }
1475         }
1476
1477         @Override
1478         public void getCustomPrinterIcon(PrinterId printerId, IPrintSpoolerCallbacks callbacks,
1479                 int sequence) throws RemoteException {
1480             Icon icon = null;
1481             try {
1482                 icon = PrintSpoolerService.this.getCustomPrinterIcon(printerId);
1483             } finally {
1484                 callbacks.onGetCustomPrinterIconResult(icon, sequence);
1485             }
1486         }
1487
1488         @Override
1489         public void clearCustomPrinterIconCache(IPrintSpoolerCallbacks callbacks,
1490                 int sequence) throws RemoteException {
1491             try {
1492                 PrintSpoolerService.this.clearCustomPrinterIconCache();
1493             } finally {
1494                 callbacks.customPrinterIconCacheCleared(sequence);
1495             }
1496         }
1497
1498     }
1499 }