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