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