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