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.IBinder;
24import android.os.Message;
25import android.os.ParcelFileDescriptor;
26import android.os.RemoteException;
27import android.print.IPrintSpooler;
28import android.print.IPrintSpoolerCallbacks;
29import android.print.IPrintSpoolerClient;
30import android.print.PageRange;
31import android.print.PrintAttributes;
32import android.print.PrintAttributes.Margins;
33import android.print.PrintAttributes.MediaSize;
34import android.print.PrintAttributes.Resolution;
35import android.print.PrintDocumentInfo;
36import android.print.PrintJobId;
37import android.print.PrintJobInfo;
38import android.print.PrintManager;
39import android.print.PrinterId;
40import android.print.PrinterInfo;
41import android.text.TextUtils;
42import android.util.ArrayMap;
43import android.util.AtomicFile;
44import android.util.Log;
45import android.util.Slog;
46import android.util.Xml;
47
48import com.android.internal.os.HandlerCaller;
49import com.android.internal.util.FastXmlSerializer;
50
51import libcore.io.IoUtils;
52
53import org.xmlpull.v1.XmlPullParser;
54import org.xmlpull.v1.XmlPullParserException;
55import org.xmlpull.v1.XmlSerializer;
56
57import java.io.File;
58import java.io.FileDescriptor;
59import java.io.FileInputStream;
60import java.io.FileNotFoundException;
61import java.io.FileOutputStream;
62import java.io.IOException;
63import java.io.PrintWriter;
64import java.util.ArrayList;
65import java.util.List;
66
67/**
68 * Service for exposing some of the {@link PrintSpooler} functionality to
69 * another process.
70 */
71public 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        private static final String ATTR_DATA_SIZE = "dataSize";
734
735        private final AtomicFile mStatePersistFile;
736
737        private boolean mWriteStateScheduled;
738
739        private PersistenceManager() {
740            mStatePersistFile = new AtomicFile(new File(getFilesDir(),
741                    PERSIST_FILE_NAME));
742        }
743
744        public void writeStateLocked() {
745            if (!PERSISTNECE_MANAGER_ENABLED) {
746                return;
747            }
748            if (mWriteStateScheduled) {
749                return;
750            }
751            mWriteStateScheduled = true;
752            new AsyncTask<Void, Void, Void>() {
753                @Override
754                protected Void doInBackground(Void... params) {
755                    synchronized (mLock) {
756                        mWriteStateScheduled = false;
757                        doWriteStateLocked();
758                    }
759                    return null;
760                }
761            }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
762        }
763
764        private void doWriteStateLocked() {
765            if (DEBUG_PERSISTENCE) {
766                Log.i(LOG_TAG, "[PERSIST START]");
767            }
768            FileOutputStream out = null;
769            try {
770                out = mStatePersistFile.startWrite();
771
772                XmlSerializer serializer = new FastXmlSerializer();
773                serializer.setOutput(out, "utf-8");
774                serializer.startDocument(null, true);
775                serializer.startTag(null, TAG_SPOOLER);
776
777                List<PrintJobInfo> printJobs = mPrintJobs;
778
779                final int printJobCount = printJobs.size();
780                for (int j = 0; j < printJobCount; j++) {
781                    PrintJobInfo printJob = printJobs.get(j);
782
783                    serializer.startTag(null, TAG_JOB);
784
785                    serializer.attribute(null, ATTR_ID, printJob.getId().flattenToString());
786                    serializer.attribute(null, ATTR_LABEL, printJob.getLabel().toString());
787                    serializer.attribute(null, ATTR_STATE, String.valueOf(printJob.getState()));
788                    serializer.attribute(null, ATTR_APP_ID, String.valueOf(printJob.getAppId()));
789                    String tag = printJob.getTag();
790                    if (tag != null) {
791                        serializer.attribute(null, ATTR_TAG, tag);
792                    }
793                    serializer.attribute(null, ATTR_CREATION_TIME, String.valueOf(
794                            printJob.getCreationTime()));
795                    serializer.attribute(null, ATTR_COPIES, String.valueOf(printJob.getCopies()));
796                    String printerName = printJob.getPrinterName();
797                    if (!TextUtils.isEmpty(printerName)) {
798                        serializer.attribute(null, ATTR_PRINTER_NAME, printerName);
799                    }
800                    String stateReason = printJob.getStateReason();
801                    if (!TextUtils.isEmpty(stateReason)) {
802                        serializer.attribute(null, ATTR_STATE_REASON, stateReason);
803                    }
804                    serializer.attribute(null, ATTR_CANCELLING, String.valueOf(
805                            printJob.isCancelling()));
806
807                    PrinterId printerId = printJob.getPrinterId();
808                    if (printerId != null) {
809                        serializer.startTag(null, TAG_PRINTER_ID);
810                        serializer.attribute(null, ATTR_LOCAL_ID, printerId.getLocalId());
811                        serializer.attribute(null, ATTR_SERVICE_NAME, printerId.getServiceName()
812                                .flattenToString());
813                        serializer.endTag(null, TAG_PRINTER_ID);
814                    }
815
816                    PageRange[] pages = printJob.getPages();
817                    if (pages != null) {
818                        for (int i = 0; i < pages.length; i++) {
819                            serializer.startTag(null, TAG_PAGE_RANGE);
820                            serializer.attribute(null, ATTR_START, String.valueOf(
821                                    pages[i].getStart()));
822                            serializer.attribute(null, ATTR_END, String.valueOf(
823                                    pages[i].getEnd()));
824                            serializer.endTag(null, TAG_PAGE_RANGE);
825                        }
826                    }
827
828                    PrintAttributes attributes = printJob.getAttributes();
829                    if (attributes != null) {
830                        serializer.startTag(null, TAG_ATTRIBUTES);
831
832                        final int colorMode = attributes.getColorMode();
833                        serializer.attribute(null, ATTR_COLOR_MODE,
834                                String.valueOf(colorMode));
835
836                        MediaSize mediaSize = attributes.getMediaSize();
837                        if (mediaSize != null) {
838                            serializer.startTag(null, TAG_MEDIA_SIZE);
839                            serializer.attribute(null, ATTR_ID, mediaSize.getId());
840                            serializer.attribute(null, ATTR_WIDTH_MILS, String.valueOf(
841                                    mediaSize.getWidthMils()));
842                            serializer.attribute(null, ATTR_HEIGHT_MILS, String.valueOf(
843                                    mediaSize.getHeightMils()));
844                            // We prefer to store only the package name and
845                            // resource id and fallback to the label.
846                            if (!TextUtils.isEmpty(mediaSize.mPackageName)
847                                    && mediaSize.mLabelResId > 0) {
848                                serializer.attribute(null, ATTR_PACKAGE_NAME,
849                                        mediaSize.mPackageName);
850                                serializer.attribute(null, ATTR_LABEL_RES_ID,
851                                        String.valueOf(mediaSize.mLabelResId));
852                            } else {
853                                serializer.attribute(null, ATTR_LABEL,
854                                        mediaSize.getLabel(getPackageManager()));
855                            }
856                            serializer.endTag(null, TAG_MEDIA_SIZE);
857                        }
858
859                        Resolution resolution = attributes.getResolution();
860                        if (resolution != null) {
861                            serializer.startTag(null, TAG_RESOLUTION);
862                            serializer.attribute(null, ATTR_ID, resolution.getId());
863                            serializer.attribute(null, ATTR_HORIZONTAL_DPI, String.valueOf(
864                                    resolution.getHorizontalDpi()));
865                            serializer.attribute(null, ATTR_VERTICAL_DPI, String.valueOf(
866                                    resolution.getVerticalDpi()));
867                            serializer.attribute(null, ATTR_LABEL,
868                                    resolution.getLabel());
869                            serializer.endTag(null, TAG_RESOLUTION);
870                        }
871
872                        Margins margins = attributes.getMinMargins();
873                        if (margins != null) {
874                            serializer.startTag(null, TAG_MARGINS);
875                            serializer.attribute(null, ATTR_LEFT_MILS, String.valueOf(
876                                    margins.getLeftMils()));
877                            serializer.attribute(null, ATTR_TOP_MILS, String.valueOf(
878                                    margins.getTopMils()));
879                            serializer.attribute(null, ATTR_RIGHT_MILS, String.valueOf(
880                                    margins.getRightMils()));
881                            serializer.attribute(null, ATTR_BOTTOM_MILS, String.valueOf(
882                                    margins.getBottomMils()));
883                            serializer.endTag(null, TAG_MARGINS);
884                        }
885
886                        serializer.endTag(null, TAG_ATTRIBUTES);
887                    }
888
889                    PrintDocumentInfo documentInfo = printJob.getDocumentInfo();
890                    if (documentInfo != null) {
891                        serializer.startTag(null, TAG_DOCUMENT_INFO);
892                        serializer.attribute(null, ATTR_NAME, documentInfo.getName());
893                        serializer.attribute(null, ATTR_CONTENT_TYPE, String.valueOf(
894                                documentInfo.getContentType()));
895                        serializer.attribute(null, ATTR_PAGE_COUNT, String.valueOf(
896                                documentInfo.getPageCount()));
897                        serializer.attribute(null, ATTR_DATA_SIZE, String.valueOf(
898                                documentInfo.getDataSize()));
899                        serializer.endTag(null, TAG_DOCUMENT_INFO);
900                    }
901
902                    serializer.endTag(null, TAG_JOB);
903
904                    if (DEBUG_PERSISTENCE) {
905                        Log.i(LOG_TAG, "[PERSISTED] " + printJob);
906                    }
907                }
908
909                serializer.endTag(null, TAG_SPOOLER);
910                serializer.endDocument();
911                mStatePersistFile.finishWrite(out);
912                if (DEBUG_PERSISTENCE) {
913                    Log.i(LOG_TAG, "[PERSIST END]");
914                }
915            } catch (IOException e) {
916                Slog.w(LOG_TAG, "Failed to write state, restoring backup.", e);
917                mStatePersistFile.failWrite(out);
918            } finally {
919                IoUtils.closeQuietly(out);
920            }
921        }
922
923        public void readStateLocked() {
924            if (!PERSISTNECE_MANAGER_ENABLED) {
925                return;
926            }
927            FileInputStream in = null;
928            try {
929                in = mStatePersistFile.openRead();
930            } catch (FileNotFoundException e) {
931                Log.i(LOG_TAG, "No existing print spooler state.");
932                return;
933            }
934            try {
935                XmlPullParser parser = Xml.newPullParser();
936                parser.setInput(in, null);
937                parseState(parser);
938            } catch (IllegalStateException ise) {
939                Slog.w(LOG_TAG, "Failed parsing ", ise);
940            } catch (NullPointerException npe) {
941                Slog.w(LOG_TAG, "Failed parsing ", npe);
942            } catch (NumberFormatException nfe) {
943                Slog.w(LOG_TAG, "Failed parsing ", nfe);
944            } catch (XmlPullParserException xppe) {
945                Slog.w(LOG_TAG, "Failed parsing ", xppe);
946            } catch (IOException ioe) {
947                Slog.w(LOG_TAG, "Failed parsing ", ioe);
948            } catch (IndexOutOfBoundsException iobe) {
949                Slog.w(LOG_TAG, "Failed parsing ", iobe);
950            } finally {
951                IoUtils.closeQuietly(in);
952            }
953        }
954
955        private void parseState(XmlPullParser parser)
956                throws IOException, XmlPullParserException {
957            parser.next();
958            skipEmptyTextTags(parser);
959            expect(parser, XmlPullParser.START_TAG, TAG_SPOOLER);
960            parser.next();
961
962            while (parsePrintJob(parser)) {
963                parser.next();
964            }
965
966            skipEmptyTextTags(parser);
967            expect(parser, XmlPullParser.END_TAG, TAG_SPOOLER);
968        }
969
970        private boolean parsePrintJob(XmlPullParser parser)
971                throws IOException, XmlPullParserException {
972            skipEmptyTextTags(parser);
973            if (!accept(parser, XmlPullParser.START_TAG, TAG_JOB)) {
974                return false;
975            }
976
977            PrintJobInfo printJob = new PrintJobInfo();
978
979            PrintJobId printJobId = PrintJobId.unflattenFromString(
980                    parser.getAttributeValue(null, ATTR_ID));
981            printJob.setId(printJobId);
982            String label = parser.getAttributeValue(null, ATTR_LABEL);
983            printJob.setLabel(label);
984            final int state = Integer.parseInt(parser.getAttributeValue(null, ATTR_STATE));
985            printJob.setState(state);
986            final int appId = Integer.parseInt(parser.getAttributeValue(null, ATTR_APP_ID));
987            printJob.setAppId(appId);
988            String tag = parser.getAttributeValue(null, ATTR_TAG);
989            printJob.setTag(tag);
990            String creationTime = parser.getAttributeValue(null, ATTR_CREATION_TIME);
991            printJob.setCreationTime(Long.parseLong(creationTime));
992            String copies = parser.getAttributeValue(null, ATTR_COPIES);
993            printJob.setCopies(Integer.parseInt(copies));
994            String printerName = parser.getAttributeValue(null, ATTR_PRINTER_NAME);
995            printJob.setPrinterName(printerName);
996            String stateReason = parser.getAttributeValue(null, ATTR_STATE_REASON);
997            printJob.setStateReason(stateReason);
998            String cancelling = parser.getAttributeValue(null, ATTR_CANCELLING);
999            printJob.setCancelling(!TextUtils.isEmpty(cancelling)
1000                    ? Boolean.parseBoolean(cancelling) : false);
1001
1002            parser.next();
1003
1004            skipEmptyTextTags(parser);
1005            if (accept(parser, XmlPullParser.START_TAG, TAG_PRINTER_ID)) {
1006                String localId = parser.getAttributeValue(null, ATTR_LOCAL_ID);
1007                ComponentName service = ComponentName.unflattenFromString(parser.getAttributeValue(
1008                        null, ATTR_SERVICE_NAME));
1009                printJob.setPrinterId(new PrinterId(service, localId));
1010                parser.next();
1011                skipEmptyTextTags(parser);
1012                expect(parser, XmlPullParser.END_TAG, TAG_PRINTER_ID);
1013                parser.next();
1014            }
1015
1016            skipEmptyTextTags(parser);
1017            List<PageRange> pageRanges = null;
1018            while (accept(parser, XmlPullParser.START_TAG, TAG_PAGE_RANGE)) {
1019                final int start = Integer.parseInt(parser.getAttributeValue(null, ATTR_START));
1020                final int end = Integer.parseInt(parser.getAttributeValue(null, ATTR_END));
1021                PageRange pageRange = new PageRange(start, end);
1022                if (pageRanges == null) {
1023                    pageRanges = new ArrayList<PageRange>();
1024                }
1025                pageRanges.add(pageRange);
1026                parser.next();
1027                skipEmptyTextTags(parser);
1028                expect(parser, XmlPullParser.END_TAG, TAG_PAGE_RANGE);
1029                parser.next();
1030            }
1031            if (pageRanges != null) {
1032                PageRange[] pageRangesArray = new PageRange[pageRanges.size()];
1033                pageRanges.toArray(pageRangesArray);
1034                printJob.setPages(pageRangesArray);
1035            }
1036
1037            skipEmptyTextTags(parser);
1038            if (accept(parser, XmlPullParser.START_TAG, TAG_ATTRIBUTES)) {
1039
1040                PrintAttributes.Builder builder = new PrintAttributes.Builder();
1041
1042                String colorMode = parser.getAttributeValue(null, ATTR_COLOR_MODE);
1043                builder.setColorMode(Integer.parseInt(colorMode));
1044
1045                parser.next();
1046
1047                skipEmptyTextTags(parser);
1048                if (accept(parser, XmlPullParser.START_TAG, TAG_MEDIA_SIZE)) {
1049                    String id = parser.getAttributeValue(null, ATTR_ID);
1050                    label = parser.getAttributeValue(null, ATTR_LABEL);
1051                    final int widthMils = Integer.parseInt(parser.getAttributeValue(null,
1052                            ATTR_WIDTH_MILS));
1053                    final int heightMils = Integer.parseInt(parser.getAttributeValue(null,
1054                            ATTR_HEIGHT_MILS));
1055                    String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
1056                    String labelResIdString = parser.getAttributeValue(null, ATTR_LABEL_RES_ID);
1057                    final int labelResId = (labelResIdString != null)
1058                            ? Integer.parseInt(labelResIdString) : 0;
1059                    label = parser.getAttributeValue(null, ATTR_LABEL);
1060                    MediaSize mediaSize = new MediaSize(id, label, packageName, labelResId,
1061                                widthMils, heightMils);
1062                    builder.setMediaSize(mediaSize);
1063                    parser.next();
1064                    skipEmptyTextTags(parser);
1065                    expect(parser, XmlPullParser.END_TAG, TAG_MEDIA_SIZE);
1066                    parser.next();
1067                }
1068
1069                skipEmptyTextTags(parser);
1070                if (accept(parser, XmlPullParser.START_TAG, TAG_RESOLUTION)) {
1071                    String id = parser.getAttributeValue(null, ATTR_ID);
1072                    label = parser.getAttributeValue(null, ATTR_LABEL);
1073                    final int horizontalDpi = Integer.parseInt(parser.getAttributeValue(null,
1074                            ATTR_HORIZONTAL_DPI));
1075                    final int verticalDpi = Integer.parseInt(parser.getAttributeValue(null,
1076                            ATTR_VERTICAL_DPI));
1077                    Resolution resolution = new Resolution(id, label, horizontalDpi, verticalDpi);
1078                    builder.setResolution(resolution);
1079                    parser.next();
1080                    skipEmptyTextTags(parser);
1081                    expect(parser, XmlPullParser.END_TAG, TAG_RESOLUTION);
1082                    parser.next();
1083                }
1084
1085                skipEmptyTextTags(parser);
1086                if (accept(parser, XmlPullParser.START_TAG, TAG_MARGINS)) {
1087                    final int leftMils = Integer.parseInt(parser.getAttributeValue(null,
1088                            ATTR_LEFT_MILS));
1089                    final int topMils = Integer.parseInt(parser.getAttributeValue(null,
1090                            ATTR_TOP_MILS));
1091                    final int rightMils = Integer.parseInt(parser.getAttributeValue(null,
1092                            ATTR_RIGHT_MILS));
1093                    final int bottomMils = Integer.parseInt(parser.getAttributeValue(null,
1094                            ATTR_BOTTOM_MILS));
1095                    Margins margins = new Margins(leftMils, topMils, rightMils, bottomMils);
1096                    builder.setMinMargins(margins);
1097                    parser.next();
1098                    skipEmptyTextTags(parser);
1099                    expect(parser, XmlPullParser.END_TAG, TAG_MARGINS);
1100                    parser.next();
1101                }
1102
1103                printJob.setAttributes(builder.build());
1104
1105                skipEmptyTextTags(parser);
1106                expect(parser, XmlPullParser.END_TAG, TAG_ATTRIBUTES);
1107                parser.next();
1108            }
1109
1110            skipEmptyTextTags(parser);
1111            if (accept(parser, XmlPullParser.START_TAG, TAG_DOCUMENT_INFO)) {
1112                String name = parser.getAttributeValue(null, ATTR_NAME);
1113                final int pageCount = Integer.parseInt(parser.getAttributeValue(null,
1114                        ATTR_PAGE_COUNT));
1115                final int contentType = Integer.parseInt(parser.getAttributeValue(null,
1116                        ATTR_CONTENT_TYPE));
1117                final int dataSize = Integer.parseInt(parser.getAttributeValue(null,
1118                        ATTR_DATA_SIZE));
1119                PrintDocumentInfo info = new PrintDocumentInfo.Builder(name)
1120                        .setPageCount(pageCount)
1121                        .setContentType(contentType).build();
1122                printJob.setDocumentInfo(info);
1123                info.setDataSize(dataSize);
1124                parser.next();
1125                skipEmptyTextTags(parser);
1126                expect(parser, XmlPullParser.END_TAG, TAG_DOCUMENT_INFO);
1127                parser.next();
1128            }
1129
1130            mPrintJobs.add(printJob);
1131
1132            if (DEBUG_PERSISTENCE) {
1133                Log.i(LOG_TAG, "[RESTORED] " + printJob);
1134            }
1135
1136            skipEmptyTextTags(parser);
1137            expect(parser, XmlPullParser.END_TAG, TAG_JOB);
1138
1139            return true;
1140        }
1141
1142        private void expect(XmlPullParser parser, int type, String tag)
1143                throws IOException, XmlPullParserException {
1144            if (!accept(parser, type, tag)) {
1145                throw new XmlPullParserException("Exepected event: " + type
1146                        + " and tag: " + tag + " but got event: " + parser.getEventType()
1147                        + " and tag:" + parser.getName());
1148            }
1149        }
1150
1151        private void skipEmptyTextTags(XmlPullParser parser)
1152                throws IOException, XmlPullParserException {
1153            while (accept(parser, XmlPullParser.TEXT, null)
1154                    && "\n".equals(parser.getText())) {
1155                parser.next();
1156            }
1157        }
1158
1159        private boolean accept(XmlPullParser parser, int type, String tag)
1160                throws IOException, XmlPullParserException {
1161            if (parser.getEventType() != type) {
1162                return false;
1163            }
1164            if (tag != null) {
1165                if (!tag.equals(parser.getName())) {
1166                    return false;
1167                }
1168            } else if (parser.getName() != null) {
1169                return false;
1170            }
1171            return true;
1172        }
1173    }
1174
1175    final class PrintSpooler extends IPrintSpooler.Stub {
1176        @Override
1177        public void getPrintJobInfos(IPrintSpoolerCallbacks callback,
1178                ComponentName componentName, int state, int appId, int sequence)
1179                throws RemoteException {
1180            List<PrintJobInfo> printJobs = null;
1181            try {
1182                printJobs = PrintSpoolerService.this.getPrintJobInfos(
1183                        componentName, state, appId);
1184            } finally {
1185                callback.onGetPrintJobInfosResult(printJobs, sequence);
1186            }
1187        }
1188
1189        @Override
1190        public void getPrintJobInfo(PrintJobId printJobId, IPrintSpoolerCallbacks callback,
1191                int appId, int sequence) throws RemoteException {
1192            PrintJobInfo printJob = null;
1193            try {
1194                printJob = PrintSpoolerService.this.getPrintJobInfo(printJobId, appId);
1195            } finally {
1196                callback.onGetPrintJobInfoResult(printJob, sequence);
1197            }
1198        }
1199
1200        @Override
1201        public void createPrintJob(PrintJobInfo printJob) {
1202            PrintSpoolerService.this.createPrintJob(printJob);
1203        }
1204
1205        @Override
1206        public void setPrintJobState(PrintJobId printJobId, int state, String error,
1207                IPrintSpoolerCallbacks callback, int sequece) throws RemoteException {
1208            boolean success = false;
1209            try {
1210                success = PrintSpoolerService.this.setPrintJobState(
1211                        printJobId, state, error);
1212            } finally {
1213                callback.onSetPrintJobStateResult(success, sequece);
1214            }
1215        }
1216
1217        @Override
1218        public void setPrintJobTag(PrintJobId printJobId, String tag,
1219                IPrintSpoolerCallbacks callback, int sequece) throws RemoteException {
1220            boolean success = false;
1221            try {
1222                success = PrintSpoolerService.this.setPrintJobTag(printJobId, tag);
1223            } finally {
1224                callback.onSetPrintJobTagResult(success, sequece);
1225            }
1226        }
1227
1228        @Override
1229        public void writePrintJobData(ParcelFileDescriptor fd, PrintJobId printJobId) {
1230            PrintSpoolerService.this.writePrintJobData(fd, printJobId);
1231        }
1232
1233        @Override
1234        public void setClient(IPrintSpoolerClient client) {
1235            Message message = mHandlerCaller.obtainMessageO(
1236                    HandlerCallerCallback.MSG_SET_CLIENT, client);
1237            mHandlerCaller.executeOrSendMessage(message);
1238        }
1239
1240        @Override
1241        public void removeObsoletePrintJobs() {
1242            PrintSpoolerService.this.removeObsoletePrintJobs();
1243        }
1244
1245        @Override
1246        protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
1247            PrintSpoolerService.this.dump(fd, writer, args);
1248        }
1249
1250        @Override
1251        public void setPrintJobCancelling(PrintJobId printJobId, boolean cancelling) {
1252            PrintSpoolerService.this.setPrintJobCancelling(printJobId, cancelling);
1253        }
1254
1255        public PrintSpoolerService getService() {
1256            return PrintSpoolerService.this;
1257        }
1258    }
1259}
1260