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