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