OSDN Git Service

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