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