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