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