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