Home | History | Annotate | Download | only in printspooler
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.printspooler;
     18 
     19 import android.app.Service;
     20 import android.content.ComponentName;
     21 import android.content.Intent;
     22 import android.os.AsyncTask;
     23 import android.os.IBinder;
     24 import android.os.Message;
     25 import android.os.ParcelFileDescriptor;
     26 import android.os.RemoteException;
     27 import android.print.IPrintSpooler;
     28 import android.print.IPrintSpoolerCallbacks;
     29 import android.print.IPrintSpoolerClient;
     30 import android.print.PageRange;
     31 import android.print.PrintAttributes;
     32 import android.print.PrintAttributes.Margins;
     33 import android.print.PrintAttributes.MediaSize;
     34 import android.print.PrintAttributes.Resolution;
     35 import android.print.PrintDocumentInfo;
     36 import android.print.PrintJobId;
     37 import android.print.PrintJobInfo;
     38 import android.print.PrintManager;
     39 import android.print.PrinterId;
     40 import android.print.PrinterInfo;
     41 import android.text.TextUtils;
     42 import android.util.ArrayMap;
     43 import android.util.AtomicFile;
     44 import android.util.Log;
     45 import android.util.Slog;
     46 import android.util.Xml;
     47 
     48 import com.android.internal.os.HandlerCaller;
     49 import com.android.internal.util.FastXmlSerializer;
     50 
     51 import libcore.io.IoUtils;
     52 
     53 import org.xmlpull.v1.XmlPullParser;
     54 import org.xmlpull.v1.XmlPullParserException;
     55 import org.xmlpull.v1.XmlSerializer;
     56 
     57 import java.io.File;
     58 import java.io.FileDescriptor;
     59 import java.io.FileInputStream;
     60 import java.io.FileNotFoundException;
     61 import java.io.FileOutputStream;
     62 import java.io.IOException;
     63 import java.io.PrintWriter;
     64 import java.util.ArrayList;
     65 import java.util.List;
     66 
     67 /**
     68  * Service for exposing some of the {@link PrintSpooler} functionality to
     69  * another process.
     70  */
     71 public final class PrintSpoolerService extends Service {
     72 
     73     private static final String LOG_TAG = "PrintSpoolerService";
     74 
     75     private static final boolean DEBUG_PRINT_JOB_LIFECYCLE = false;
     76 
     77     private static final boolean DEBUG_PERSISTENCE = false;
     78 
     79     private static final boolean PERSISTNECE_MANAGER_ENABLED = true;
     80 
     81     private static final long CHECK_ALL_PRINTJOBS_HANDLED_DELAY = 5000;
     82 
     83     private static final String PRINT_JOB_FILE_PREFIX = "print_job_";
     84 
     85     private static final String PRINT_FILE_EXTENSION = "pdf";
     86 
     87     private static final Object sLock = new Object();
     88 
     89     private final Object mLock = new Object();
     90 
     91     private final List<PrintJobInfo> mPrintJobs = new ArrayList<PrintJobInfo>();
     92 
     93     private static PrintSpoolerService sInstance;
     94 
     95     private IPrintSpoolerClient mClient;
     96 
     97     private HandlerCaller mHandlerCaller;
     98 
     99     private PersistenceManager mPersistanceManager;
    100 
    101     private NotificationController mNotificationController;
    102 
    103     public static PrintSpoolerService peekInstance() {
    104         synchronized (sLock) {
    105             return sInstance;
    106         }
    107     }
    108 
    109     @Override
    110     public void onCreate() {
    111         super.onCreate();
    112         mHandlerCaller = new HandlerCaller(this, getMainLooper(),
    113                 new HandlerCallerCallback(), false);
    114 
    115         mPersistanceManager = new PersistenceManager();
    116         mNotificationController = new NotificationController(PrintSpoolerService.this);
    117 
    118         synchronized (mLock) {
    119             mPersistanceManager.readStateLocked();
    120             handleReadPrintJobsLocked();
    121         }
    122 
    123         synchronized (sLock) {
    124             sInstance = this;
    125         }
    126     }
    127 
    128     @Override
    129     public IBinder onBind(Intent intent) {
    130         return new PrintSpooler();
    131     }
    132 
    133     @Override
    134     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    135         synchronized (mLock) {
    136             String prefix = (args.length > 0) ? args[0] : "";
    137             String tab = "  ";
    138 
    139             pw.append(prefix).append("print jobs:").println();
    140             final int printJobCount = mPrintJobs.size();
    141             for (int i = 0; i < printJobCount; i++) {
    142                 PrintJobInfo printJob = mPrintJobs.get(i);
    143                 pw.append(prefix).append(tab).append(printJob.toString());
    144                 pw.println();
    145             }
    146 
    147             pw.append(prefix).append("print job files:").println();
    148             File[] files = getFilesDir().listFiles();
    149             if (files != null) {
    150                 final int fileCount = files.length;
    151                 for (int i = 0; i < fileCount; i++) {
    152                     File file = files[i];
    153                     if (file.isFile() && file.getName().startsWith(PRINT_JOB_FILE_PREFIX)) {
    154                         pw.append(prefix).append(tab).append(file.getName()).println();
    155                     }
    156                 }
    157             }
    158         }
    159     }
    160 
    161     private void sendOnPrintJobQueued(PrintJobInfo printJob) {
    162         Message message = mHandlerCaller.obtainMessageO(
    163                 HandlerCallerCallback.MSG_ON_PRINT_JOB_QUEUED, printJob);
    164         mHandlerCaller.executeOrSendMessage(message);
    165     }
    166 
    167     private void sendOnAllPrintJobsForServiceHandled(ComponentName service) {
    168         Message message = mHandlerCaller.obtainMessageO(
    169                 HandlerCallerCallback.MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED, service);
    170         mHandlerCaller.executeOrSendMessage(message);
    171     }
    172 
    173     private void sendOnAllPrintJobsHandled() {
    174         Message message = mHandlerCaller.obtainMessage(
    175                 HandlerCallerCallback.MSG_ON_ALL_PRINT_JOBS_HANDLED);
    176         mHandlerCaller.executeOrSendMessage(message);
    177     }
    178 
    179     private final class HandlerCallerCallback implements HandlerCaller.Callback {
    180         public static final int MSG_SET_CLIENT = 1;
    181         public static final int MSG_ON_PRINT_JOB_QUEUED = 2;
    182         public static final int MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED = 3;
    183         public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 4;
    184         public static final int MSG_CHECK_ALL_PRINTJOBS_HANDLED = 5;
    185         public static final int MSG_ON_PRINT_JOB_STATE_CHANGED = 6;
    186 
    187         @Override
    188         public void executeMessage(Message message) {
    189             switch (message.what) {
    190                 case MSG_SET_CLIENT: {
    191                     synchronized (mLock) {
    192                         mClient = (IPrintSpoolerClient) message.obj;
    193                         if (mClient != null) {
    194                             Message msg = mHandlerCaller.obtainMessage(
    195                                     HandlerCallerCallback.MSG_CHECK_ALL_PRINTJOBS_HANDLED);
    196                             mHandlerCaller.sendMessageDelayed(msg,
    197                                     CHECK_ALL_PRINTJOBS_HANDLED_DELAY);
    198                         }
    199                     }
    200                 } break;
    201 
    202                 case MSG_ON_PRINT_JOB_QUEUED: {
    203                     PrintJobInfo printJob = (PrintJobInfo) message.obj;
    204                     if (mClient != null) {
    205                         try {
    206                             mClient.onPrintJobQueued(printJob);
    207                         } catch (RemoteException re) {
    208                             Slog.e(LOG_TAG, "Error notify for a queued print job.", re);
    209                         }
    210                     }
    211                 } break;
    212 
    213                 case MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED: {
    214                     ComponentName service = (ComponentName) message.obj;
    215                     if (mClient != null) {
    216                         try {
    217                             mClient.onAllPrintJobsForServiceHandled(service);
    218                         } catch (RemoteException re) {
    219                             Slog.e(LOG_TAG, "Error notify for all print jobs per service"
    220                                     + " handled.", re);
    221                         }
    222                     }
    223                 } break;
    224 
    225                 case MSG_ON_ALL_PRINT_JOBS_HANDLED: {
    226                     if (mClient != null) {
    227                         try {
    228                             mClient.onAllPrintJobsHandled();
    229                         } catch (RemoteException re) {
    230                             Slog.e(LOG_TAG, "Error notify for all print job handled.", re);
    231                         }
    232                     }
    233                 } break;
    234 
    235                 case MSG_CHECK_ALL_PRINTJOBS_HANDLED: {
    236                     checkAllPrintJobsHandled();
    237                 } break;
    238 
    239                 case MSG_ON_PRINT_JOB_STATE_CHANGED: {
    240                     if (mClient != null) {
    241                         PrintJobInfo printJob = (PrintJobInfo) message.obj;
    242                         try {
    243                             mClient.onPrintJobStateChanged(printJob);
    244                         } catch (RemoteException re) {
    245                             Slog.e(LOG_TAG, "Error notify for print job state change.", re);
    246                         }
    247                     }
    248                 } break;
    249             }
    250         }
    251     }
    252 
    253     public List<PrintJobInfo> getPrintJobInfos(ComponentName componentName,
    254             int state, int appId) {
    255         List<PrintJobInfo> foundPrintJobs = null;
    256         synchronized (mLock) {
    257             final int printJobCount = mPrintJobs.size();
    258             for (int i = 0; i < printJobCount; i++) {
    259                 PrintJobInfo printJob = mPrintJobs.get(i);
    260                 PrinterId printerId = printJob.getPrinterId();
    261                 final boolean sameComponent = (componentName == null
    262                         || (printerId != null
    263                         && componentName.equals(printerId.getServiceName())));
    264                 final boolean sameAppId = appId == PrintManager.APP_ID_ANY
    265                         || printJob.getAppId() == appId;
    266                 final boolean sameState = (state == printJob.getState())
    267                         || (state == PrintJobInfo.STATE_ANY)
    268                         || (state == PrintJobInfo.STATE_ANY_VISIBLE_TO_CLIENTS
    269                             && isStateVisibleToUser(printJob.getState()))
    270                         || (state == PrintJobInfo.STATE_ANY_ACTIVE
    271                             && isActiveState(printJob.getState()))
    272                         || (state == PrintJobInfo.STATE_ANY_SCHEDULED
    273                             && isScheduledState(printJob.getState()));
    274                 if (sameComponent && sameAppId && sameState) {
    275                     if (foundPrintJobs == null) {
    276                         foundPrintJobs = new ArrayList<PrintJobInfo>();
    277                     }
    278                     foundPrintJobs.add(printJob);
    279                 }
    280             }
    281         }
    282         return foundPrintJobs;
    283     }
    284 
    285     private boolean isStateVisibleToUser(int state) {
    286         return (isActiveState(state) && (state == PrintJobInfo.STATE_FAILED
    287                 || state == PrintJobInfo.STATE_COMPLETED || state == PrintJobInfo.STATE_CANCELED
    288                 || state == PrintJobInfo.STATE_BLOCKED));
    289     }
    290 
    291     public PrintJobInfo getPrintJobInfo(PrintJobId printJobId, int appId) {
    292         synchronized (mLock) {
    293             final int printJobCount = mPrintJobs.size();
    294             for (int i = 0; i < printJobCount; i++) {
    295                 PrintJobInfo printJob = mPrintJobs.get(i);
    296                 if (printJob.getId().equals(printJobId)
    297                         && (appId == PrintManager.APP_ID_ANY
    298                         || appId == printJob.getAppId())) {
    299                     return printJob;
    300                 }
    301             }
    302             return null;
    303         }
    304     }
    305 
    306     public void createPrintJob(PrintJobInfo printJob) {
    307         synchronized (mLock) {
    308             addPrintJobLocked(printJob);
    309             setPrintJobState(printJob.getId(), PrintJobInfo.STATE_CREATED, null);
    310 
    311             Message message = mHandlerCaller.obtainMessageO(
    312                     HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED,
    313                     printJob);
    314             mHandlerCaller.executeOrSendMessage(message);
    315         }
    316     }
    317 
    318     private void handleReadPrintJobsLocked() {
    319         // Make a map with the files for a print job since we may have
    320         // to delete some. One example of getting orphan files if the
    321         // spooler crashes while constructing a print job. We do not
    322         // persist partially populated print jobs under construction to
    323         // avoid special handling for various attributes missing.
    324         ArrayMap<PrintJobId, File> fileForJobMap = null;
    325         File[] files = getFilesDir().listFiles();
    326         if (files != null) {
    327             final int fileCount = files.length;
    328             for (int i = 0; i < fileCount; i++) {
    329                 File file = files[i];
    330                 if (file.isFile() && file.getName().startsWith(PRINT_JOB_FILE_PREFIX)) {
    331                     if (fileForJobMap == null) {
    332                         fileForJobMap = new ArrayMap<PrintJobId, File>();
    333                     }
    334                     String printJobIdString = file.getName().substring(
    335                             PRINT_JOB_FILE_PREFIX.length(),
    336                             file.getName().indexOf('.'));
    337                     PrintJobId printJobId = PrintJobId.unflattenFromString(
    338                             printJobIdString);
    339                     fileForJobMap.put(printJobId, file);
    340                 }
    341             }
    342         }
    343 
    344         final int printJobCount = mPrintJobs.size();
    345         for (int i = 0; i < printJobCount; i++) {
    346             PrintJobInfo printJob = mPrintJobs.get(i);
    347 
    348             // We want to have only the orphan files at the end.
    349             if (fileForJobMap != null) {
    350                 fileForJobMap.remove(printJob.getId());
    351             }
    352 
    353             switch (printJob.getState()) {
    354                 case PrintJobInfo.STATE_QUEUED:
    355                 case PrintJobInfo.STATE_STARTED:
    356                 case PrintJobInfo.STATE_BLOCKED: {
    357                     // We have a print job that was queued or started or blocked in
    358                     // the past but the device battery died or a crash occurred. In
    359                     // this case we assume the print job failed and let the user
    360                     // decide whether to restart the job or just cancel it.
    361                     setPrintJobState(printJob.getId(), PrintJobInfo.STATE_FAILED,
    362                             getString(R.string.no_connection_to_printer));
    363                 } break;
    364             }
    365         }
    366 
    367         if (!mPrintJobs.isEmpty()) {
    368             // Update the notification.
    369             mNotificationController.onUpdateNotifications(mPrintJobs);
    370         }
    371 
    372         // Delete the orphan files.
    373         if (fileForJobMap != null) {
    374             final int orphanFileCount = fileForJobMap.size();
    375             for (int i = 0; i < orphanFileCount; i++) {
    376                 File file = fileForJobMap.valueAt(i);
    377                 file.delete();
    378             }
    379         }
    380     }
    381 
    382     public void checkAllPrintJobsHandled() {
    383         synchronized (mLock) {
    384             if (!hasActivePrintJobsLocked()) {
    385                 notifyOnAllPrintJobsHandled();
    386             }
    387         }
    388     }
    389 
    390     public void writePrintJobData(final ParcelFileDescriptor fd, final PrintJobId printJobId) {
    391         final PrintJobInfo printJob;
    392         synchronized (mLock) {
    393             printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
    394         }
    395         new AsyncTask<Void, Void, Void>() {
    396             @Override
    397             protected Void doInBackground(Void... params) {
    398                 FileInputStream in = null;
    399                 FileOutputStream out = null;
    400                 try {
    401                     if (printJob != null) {
    402                         File file = generateFileForPrintJob(printJobId);
    403                         in = new FileInputStream(file);
    404                         out = new FileOutputStream(fd.getFileDescriptor());
    405                     }
    406                     final byte[] buffer = new byte[8192];
    407                     while (true) {
    408                         final int readByteCount = in.read(buffer);
    409                         if (readByteCount < 0) {
    410                             return null;
    411                         }
    412                         out.write(buffer, 0, readByteCount);
    413                     }
    414                 } catch (FileNotFoundException fnfe) {
    415                     Log.e(LOG_TAG, "Error writing print job data!", fnfe);
    416                 } catch (IOException ioe) {
    417                     Log.e(LOG_TAG, "Error writing print job data!", ioe);
    418                 } finally {
    419                     IoUtils.closeQuietly(in);
    420                     IoUtils.closeQuietly(out);
    421                     IoUtils.closeQuietly(fd);
    422                 }
    423                 Log.i(LOG_TAG, "[END WRITE]");
    424                 return null;
    425             }
    426         }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
    427     }
    428 
    429     public File generateFileForPrintJob(PrintJobId printJobId) {
    430         return new File(getFilesDir(), PRINT_JOB_FILE_PREFIX
    431                 + printJobId.flattenToString() + "." + PRINT_FILE_EXTENSION);
    432     }
    433 
    434     private void addPrintJobLocked(PrintJobInfo printJob) {
    435         mPrintJobs.add(printJob);
    436         if (DEBUG_PRINT_JOB_LIFECYCLE) {
    437             Slog.i(LOG_TAG, "[ADD] " + printJob);
    438         }
    439     }
    440 
    441     private void removeObsoletePrintJobs() {
    442         synchronized (mLock) {
    443             final int printJobCount = mPrintJobs.size();
    444             for (int i = printJobCount - 1; i >= 0; i--) {
    445                 PrintJobInfo printJob = mPrintJobs.get(i);
    446                 if (isObsoleteState(printJob.getState())) {
    447                     mPrintJobs.remove(i);
    448                     if (DEBUG_PRINT_JOB_LIFECYCLE) {
    449                         Slog.i(LOG_TAG, "[REMOVE] " + printJob.getId().flattenToString());
    450                     }
    451                     removePrintJobFileLocked(printJob.getId());
    452                 }
    453             }
    454             mPersistanceManager.writeStateLocked();
    455         }
    456     }
    457 
    458     private void removePrintJobFileLocked(PrintJobId printJobId) {
    459         File file = generateFileForPrintJob(printJobId);
    460         if (file.exists()) {
    461             file.delete();
    462             if (DEBUG_PRINT_JOB_LIFECYCLE) {
    463                 Slog.i(LOG_TAG, "[REMOVE FILE FOR] " + printJobId);
    464             }
    465         }
    466     }
    467 
    468     public boolean setPrintJobState(PrintJobId printJobId, int state, String error) {
    469         boolean success = false;
    470 
    471         synchronized (mLock) {
    472             PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
    473             if (printJob != null) {
    474                 final int oldState = printJob.getState();
    475                 if (oldState == state) {
    476                     return false;
    477                 }
    478 
    479                 success = true;
    480 
    481                 printJob.setState(state);
    482                 printJob.setStateReason(error);
    483                 printJob.setCancelling(false);
    484 
    485                 if (DEBUG_PRINT_JOB_LIFECYCLE) {
    486                     Slog.i(LOG_TAG, "[STATE CHANGED] " + printJob);
    487                 }
    488 
    489                 switch (state) {
    490                     case PrintJobInfo.STATE_COMPLETED:
    491                     case PrintJobInfo.STATE_CANCELED:
    492                         mPrintJobs.remove(printJob);
    493                         removePrintJobFileLocked(printJob.getId());
    494                         // $fall-through$
    495 
    496                     case PrintJobInfo.STATE_FAILED: {
    497                         PrinterId printerId = printJob.getPrinterId();
    498                         if (printerId != null) {
    499                             ComponentName service = printerId.getServiceName();
    500                             if (!hasActivePrintJobsForServiceLocked(service)) {
    501                                 sendOnAllPrintJobsForServiceHandled(service);
    502                             }
    503                         }
    504                     } break;
    505 
    506                     case PrintJobInfo.STATE_QUEUED: {
    507                         sendOnPrintJobQueued(new PrintJobInfo(printJob));
    508                     }  break;
    509                 }
    510 
    511                 if (shouldPersistPrintJob(printJob)) {
    512                     mPersistanceManager.writeStateLocked();
    513                 }
    514 
    515                 if (!hasActivePrintJobsLocked()) {
    516                     notifyOnAllPrintJobsHandled();
    517                 }
    518 
    519                 Message message = mHandlerCaller.obtainMessageO(
    520                         HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED,
    521                         printJob);
    522                 mHandlerCaller.executeOrSendMessage(message);
    523 
    524                 mNotificationController.onUpdateNotifications(mPrintJobs);
    525             }
    526         }
    527 
    528         return success;
    529     }
    530 
    531     public boolean hasActivePrintJobsLocked() {
    532         final int printJobCount = mPrintJobs.size();
    533         for (int i = 0; i < printJobCount; i++) {
    534             PrintJobInfo printJob = mPrintJobs.get(i);
    535             if (isActiveState(printJob.getState())) {
    536                 return true;
    537             }
    538         }
    539         return false;
    540     }
    541 
    542     public boolean hasActivePrintJobsForServiceLocked(ComponentName service) {
    543         final int printJobCount = mPrintJobs.size();
    544         for (int i = 0; i < printJobCount; i++) {
    545             PrintJobInfo printJob = mPrintJobs.get(i);
    546             if (isActiveState(printJob.getState())
    547                     && printJob.getPrinterId().getServiceName().equals(service)) {
    548                 return true;
    549             }
    550         }
    551         return false;
    552     }
    553 
    554     private boolean isObsoleteState(int printJobState) {
    555         return (isTeminalState(printJobState)
    556                 || printJobState == PrintJobInfo.STATE_QUEUED);
    557     }
    558 
    559     private boolean isScheduledState(int printJobState) {
    560         return printJobState == PrintJobInfo.STATE_QUEUED
    561                 || printJobState == PrintJobInfo.STATE_STARTED
    562                 || printJobState == PrintJobInfo.STATE_BLOCKED;
    563     }
    564 
    565     private boolean isActiveState(int printJobState) {
    566         return printJobState == PrintJobInfo.STATE_CREATED
    567                 || printJobState == PrintJobInfo.STATE_QUEUED
    568                 || printJobState == PrintJobInfo.STATE_STARTED
    569                 || printJobState == PrintJobInfo.STATE_BLOCKED;
    570     }
    571 
    572     private boolean isTeminalState(int printJobState) {
    573         return printJobState == PrintJobInfo.STATE_COMPLETED
    574                 || printJobState == PrintJobInfo.STATE_CANCELED;
    575     }
    576 
    577     public boolean setPrintJobTag(PrintJobId printJobId, String tag) {
    578         synchronized (mLock) {
    579             PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
    580             if (printJob != null) {
    581                 String printJobTag = printJob.getTag();
    582                 if (printJobTag == null) {
    583                     if (tag == null) {
    584                         return false;
    585                     }
    586                 } else if (printJobTag.equals(tag)) {
    587                     return false;
    588                 }
    589                 printJob.setTag(tag);
    590                 if (shouldPersistPrintJob(printJob)) {
    591                     mPersistanceManager.writeStateLocked();
    592                 }
    593                 return true;
    594             }
    595         }
    596         return false;
    597     }
    598 
    599     public void setPrintJobCancelling(PrintJobId printJobId, boolean cancelling) {
    600         synchronized (mLock) {
    601             PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
    602             if (printJob != null) {
    603                 printJob.setCancelling(cancelling);
    604                 if (shouldPersistPrintJob(printJob)) {
    605                     mPersistanceManager.writeStateLocked();
    606                 }
    607                 mNotificationController.onUpdateNotifications(mPrintJobs);
    608 
    609                 Message message = mHandlerCaller.obtainMessageO(
    610                         HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED,
    611                         printJob);
    612                 mHandlerCaller.executeOrSendMessage(message);
    613             }
    614         }
    615     }
    616 
    617     public void setPrintJobCopiesNoPersistence(PrintJobId printJobId, int copies) {
    618         synchronized (mLock) {
    619             PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
    620             if (printJob != null) {
    621                 printJob.setCopies(copies);
    622             }
    623         }
    624     }
    625 
    626     public void setPrintJobPrintDocumentInfoNoPersistence(PrintJobId printJobId,
    627             PrintDocumentInfo info) {
    628         synchronized (mLock) {
    629             PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
    630             if (printJob != null) {
    631                 printJob.setDocumentInfo(info);
    632             }
    633         }
    634     }
    635 
    636     public void setPrintJobAttributesNoPersistence(PrintJobId printJobId,
    637             PrintAttributes attributes) {
    638         synchronized (mLock) {
    639             PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
    640             if (printJob != null) {
    641                 printJob.setAttributes(attributes);
    642             }
    643         }
    644     }
    645 
    646     public void setPrintJobPrinterNoPersistence(PrintJobId printJobId, PrinterInfo printer) {
    647         synchronized (mLock) {
    648             PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
    649             if (printJob != null) {
    650                 printJob.setPrinterId(printer.getId());
    651                 printJob.setPrinterName(printer.getName());
    652             }
    653         }
    654     }
    655 
    656     public void setPrintJobPagesNoPersistence(PrintJobId printJobId, PageRange[] pages) {
    657         synchronized (mLock) {
    658             PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
    659             if (printJob != null) {
    660                 printJob.setPages(pages);
    661             }
    662         }
    663     }
    664 
    665     private boolean shouldPersistPrintJob(PrintJobInfo printJob) {
    666         return printJob.getState() >= PrintJobInfo.STATE_QUEUED;
    667     }
    668 
    669     private void notifyOnAllPrintJobsHandled() {
    670         // This has to run on the tread that is persisting the current state
    671         // since this call may result in the system unbinding from the spooler
    672         // and as a result the spooler process may get killed before the write
    673         // completes.
    674         new AsyncTask<Void, Void, Void>() {
    675             @Override
    676             protected Void doInBackground(Void... params) {
    677                 sendOnAllPrintJobsHandled();
    678                 return null;
    679             }
    680         }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
    681     }
    682 
    683     private final class PersistenceManager {
    684         private static final String PERSIST_FILE_NAME = "print_spooler_state.xml";
    685 
    686         private static final String TAG_SPOOLER = "spooler";
    687         private static final String TAG_JOB = "job";
    688 
    689         private static final String TAG_PRINTER_ID = "printerId";
    690         private static final String TAG_PAGE_RANGE = "pageRange";
    691         private static final String TAG_ATTRIBUTES = "attributes";
    692         private static final String TAG_DOCUMENT_INFO = "documentInfo";
    693 
    694         private static final String ATTR_ID = "id";
    695         private static final String ATTR_LABEL = "label";
    696         private static final String ATTR_LABEL_RES_ID = "labelResId";
    697         private static final String ATTR_PACKAGE_NAME = "packageName";
    698         private static final String ATTR_STATE = "state";
    699         private static final String ATTR_APP_ID = "appId";
    700         private static final String ATTR_TAG = "tag";
    701         private static final String ATTR_CREATION_TIME = "creationTime";
    702         private static final String ATTR_COPIES = "copies";
    703         private static final String ATTR_PRINTER_NAME = "printerName";
    704         private static final String ATTR_STATE_REASON = "stateReason";
    705         private static final String ATTR_CANCELLING = "cancelling";
    706 
    707         private static final String TAG_MEDIA_SIZE = "mediaSize";
    708         private static final String TAG_RESOLUTION = "resolution";
    709         private static final String TAG_MARGINS = "margins";
    710 
    711         private static final String ATTR_COLOR_MODE = "colorMode";
    712 
    713         private static final String ATTR_LOCAL_ID = "localId";
    714         private static final String ATTR_SERVICE_NAME = "serviceName";
    715 
    716         private static final String ATTR_WIDTH_MILS = "widthMils";
    717         private static final String ATTR_HEIGHT_MILS = "heightMils";
    718 
    719         private static final String ATTR_HORIZONTAL_DPI = "horizontalDip";
    720         private static final String ATTR_VERTICAL_DPI = "verticalDpi";
    721 
    722         private static final String ATTR_LEFT_MILS = "leftMils";
    723         private static final String ATTR_TOP_MILS = "topMils";
    724         private static final String ATTR_RIGHT_MILS = "rightMils";
    725         private static final String ATTR_BOTTOM_MILS = "bottomMils";
    726 
    727         private static final String ATTR_START = "start";
    728         private static final String ATTR_END = "end";
    729 
    730         private static final String ATTR_NAME = "name";
    731         private static final String ATTR_PAGE_COUNT = "pageCount";
    732         private static final String ATTR_CONTENT_TYPE = "contentType";
    733         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