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