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