2 * Copyright (C) 2013 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.printspooler.model;
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;
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;
60 import libcore.io.IoUtils;
62 import org.xmlpull.v1.XmlPullParser;
63 import org.xmlpull.v1.XmlPullParserException;
64 import org.xmlpull.v1.XmlSerializer;
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;
79 * Service for exposing some of the {@link PrintSpooler} functionality to
82 public final class PrintSpoolerService extends Service {
84 private static final String LOG_TAG = "PrintSpoolerService";
86 private static final boolean DEBUG_PRINT_JOB_LIFECYCLE = false;
88 private static final boolean DEBUG_PERSISTENCE = false;
90 private static final boolean PERSISTENCE_MANAGER_ENABLED = true;
92 private static final long CHECK_ALL_PRINTJOBS_HANDLED_DELAY = 5000;
94 private static final String PRINT_JOB_FILE_PREFIX = "print_job_";
96 private static final String PRINT_FILE_EXTENSION = "pdf";
98 private static final Object sLock = new Object();
100 private final Object mLock = new Object();
102 private final List<PrintJobInfo> mPrintJobs = new ArrayList<>();
104 private static PrintSpoolerService sInstance;
106 private IPrintSpoolerClient mClient;
108 private HandlerCaller mHandlerCaller;
110 private PersistenceManager mPersistanceManager;
112 private NotificationController mNotificationController;
114 /** Cache for custom printer icons loaded from the print service */
115 private CustomPrinterIconCache mCustomIconCache;
117 public static PrintSpoolerService peekInstance() {
118 synchronized (sLock) {
124 public void onCreate() {
126 mHandlerCaller = new HandlerCaller(this, getMainLooper(),
127 new HandlerCallerCallback(), false);
129 mPersistanceManager = new PersistenceManager();
130 mNotificationController = new NotificationController(PrintSpoolerService.this);
131 mCustomIconCache = new CustomPrinterIconCache(getCacheDir());
133 synchronized (mLock) {
134 mPersistanceManager.readStateLocked();
135 handleReadPrintJobsLocked();
138 synchronized (sLock) {
144 public void onDestroy() {
149 public IBinder onBind(Intent intent) {
150 return new PrintSpooler();
154 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
155 String prefix = (args.length > 0) ? args[0] : "";
158 synchronized (mLock) {
159 pw.append(prefix).append("print jobs:").println();
160 final int printJobCount = mPrintJobs.size();
161 for (int i = 0; i < printJobCount; i++) {
162 PrintJobInfo printJob = mPrintJobs.get(i);
163 pw.append(prefix).append(tab).append(printJob.toString());
167 pw.append(prefix).append("print job files:").println();
168 File[] files = getFilesDir().listFiles();
170 final int fileCount = files.length;
171 for (int i = 0; i < fileCount; i++) {
172 File file = files[i];
173 if (file.isFile() && file.getName().startsWith(PRINT_JOB_FILE_PREFIX)) {
174 pw.append(prefix).append(tab).append(file.getName()).println();
180 pw.append(prefix).append("approved print services:").println();
181 Set<String> approvedPrintServices = (new ApprovedPrintServices(this)).getApprovedServices();
182 if (approvedPrintServices != null) {
183 for (String approvedService : approvedPrintServices) {
184 pw.append(prefix).append(tab).append(approvedService).println();
189 private void sendOnPrintJobQueued(PrintJobInfo printJob) {
190 Message message = mHandlerCaller.obtainMessageO(
191 HandlerCallerCallback.MSG_ON_PRINT_JOB_QUEUED, printJob);
192 mHandlerCaller.executeOrSendMessage(message);
195 private void sendOnAllPrintJobsForServiceHandled(ComponentName service) {
196 Message message = mHandlerCaller.obtainMessageO(
197 HandlerCallerCallback.MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED, service);
198 mHandlerCaller.executeOrSendMessage(message);
201 private void sendOnAllPrintJobsHandled() {
202 Message message = mHandlerCaller.obtainMessage(
203 HandlerCallerCallback.MSG_ON_ALL_PRINT_JOBS_HANDLED);
204 mHandlerCaller.executeOrSendMessage(message);
207 private final class HandlerCallerCallback implements HandlerCaller.Callback {
208 public static final int MSG_SET_CLIENT = 1;
209 public static final int MSG_ON_PRINT_JOB_QUEUED = 2;
210 public static final int MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED = 3;
211 public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 4;
212 public static final int MSG_CHECK_ALL_PRINTJOBS_HANDLED = 5;
213 public static final int MSG_ON_PRINT_JOB_STATE_CHANGED = 6;
216 public void executeMessage(Message message) {
217 switch (message.what) {
218 case MSG_SET_CLIENT: {
219 synchronized (mLock) {
220 mClient = (IPrintSpoolerClient) message.obj;
221 if (mClient != null) {
222 Message msg = mHandlerCaller.obtainMessage(
223 HandlerCallerCallback.MSG_CHECK_ALL_PRINTJOBS_HANDLED);
224 mHandlerCaller.sendMessageDelayed(msg,
225 CHECK_ALL_PRINTJOBS_HANDLED_DELAY);
230 case MSG_ON_PRINT_JOB_QUEUED: {
231 PrintJobInfo printJob = (PrintJobInfo) message.obj;
232 if (mClient != null) {
234 mClient.onPrintJobQueued(printJob);
235 } catch (RemoteException re) {
236 Slog.e(LOG_TAG, "Error notify for a queued print job.", re);
241 case MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED: {
242 ComponentName service = (ComponentName) message.obj;
243 if (mClient != null) {
245 mClient.onAllPrintJobsForServiceHandled(service);
246 } catch (RemoteException re) {
247 Slog.e(LOG_TAG, "Error notify for all print jobs per service"
253 case MSG_ON_ALL_PRINT_JOBS_HANDLED: {
254 if (mClient != null) {
256 mClient.onAllPrintJobsHandled();
257 } catch (RemoteException re) {
258 Slog.e(LOG_TAG, "Error notify for all print job handled.", re);
263 case MSG_CHECK_ALL_PRINTJOBS_HANDLED: {
264 checkAllPrintJobsHandled();
267 case MSG_ON_PRINT_JOB_STATE_CHANGED: {
268 if (mClient != null) {
269 PrintJobInfo printJob = (PrintJobInfo) message.obj;
271 mClient.onPrintJobStateChanged(printJob);
272 } catch (RemoteException re) {
273 Slog.e(LOG_TAG, "Error notify for print job state change.", re);
281 public List<PrintJobInfo> getPrintJobInfos(ComponentName componentName,
282 int state, int appId) {
283 List<PrintJobInfo> foundPrintJobs = null;
284 synchronized (mLock) {
285 final int printJobCount = mPrintJobs.size();
286 for (int i = 0; i < printJobCount; i++) {
287 PrintJobInfo printJob = mPrintJobs.get(i);
288 PrinterId printerId = printJob.getPrinterId();
289 final boolean sameComponent = (componentName == null
290 || (printerId != null
291 && componentName.equals(printerId.getServiceName())));
292 final boolean sameAppId = appId == PrintManager.APP_ID_ANY
293 || printJob.getAppId() == appId;
294 final boolean sameState = (state == printJob.getState())
295 || (state == PrintJobInfo.STATE_ANY)
296 || (state == PrintJobInfo.STATE_ANY_VISIBLE_TO_CLIENTS
297 && isStateVisibleToUser(printJob.getState()))
298 || (state == PrintJobInfo.STATE_ANY_ACTIVE
299 && isActiveState(printJob.getState()))
300 || (state == PrintJobInfo.STATE_ANY_SCHEDULED
301 && isScheduledState(printJob.getState()));
302 if (sameComponent && sameAppId && sameState) {
303 if (foundPrintJobs == null) {
304 foundPrintJobs = new ArrayList<>();
306 foundPrintJobs.add(printJob);
310 return foundPrintJobs;
313 private boolean isStateVisibleToUser(int state) {
314 return (isActiveState(state) && (state == PrintJobInfo.STATE_FAILED
315 || state == PrintJobInfo.STATE_COMPLETED || state == PrintJobInfo.STATE_CANCELED
316 || state == PrintJobInfo.STATE_BLOCKED));
319 public PrintJobInfo getPrintJobInfo(PrintJobId printJobId, int appId) {
320 synchronized (mLock) {
321 final int printJobCount = mPrintJobs.size();
322 for (int i = 0; i < printJobCount; i++) {
323 PrintJobInfo printJob = mPrintJobs.get(i);
324 if (printJob.getId().equals(printJobId)
325 && (appId == PrintManager.APP_ID_ANY
326 || appId == printJob.getAppId())) {
334 public void createPrintJob(PrintJobInfo printJob) {
335 synchronized (mLock) {
336 addPrintJobLocked(printJob);
337 setPrintJobState(printJob.getId(), PrintJobInfo.STATE_CREATED, null);
339 Message message = mHandlerCaller.obtainMessageO(
340 HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED,
342 mHandlerCaller.executeOrSendMessage(message);
346 private void handleReadPrintJobsLocked() {
347 // Make a map with the files for a print job since we may have
348 // to delete some. One example of getting orphan files if the
349 // spooler crashes while constructing a print job. We do not
350 // persist partially populated print jobs under construction to
351 // avoid special handling for various attributes missing.
352 ArrayMap<PrintJobId, File> fileForJobMap = null;
353 File[] files = getFilesDir().listFiles();
355 final int fileCount = files.length;
356 for (int i = 0; i < fileCount; i++) {
357 File file = files[i];
358 if (file.isFile() && file.getName().startsWith(PRINT_JOB_FILE_PREFIX)) {
359 if (fileForJobMap == null) {
360 fileForJobMap = new ArrayMap<PrintJobId, File>();
362 String printJobIdString = file.getName().substring(
363 PRINT_JOB_FILE_PREFIX.length(),
364 file.getName().indexOf('.'));
365 PrintJobId printJobId = PrintJobId.unflattenFromString(
367 fileForJobMap.put(printJobId, file);
372 final int printJobCount = mPrintJobs.size();
373 for (int i = 0; i < printJobCount; i++) {
374 PrintJobInfo printJob = mPrintJobs.get(i);
376 // We want to have only the orphan files at the end.
377 if (fileForJobMap != null) {
378 fileForJobMap.remove(printJob.getId());
381 switch (printJob.getState()) {
382 case PrintJobInfo.STATE_QUEUED:
383 case PrintJobInfo.STATE_STARTED:
384 case PrintJobInfo.STATE_BLOCKED: {
385 // We have a print job that was queued or started or blocked in
386 // the past but the device battery died or a crash occurred. In
387 // this case we assume the print job failed and let the user
388 // decide whether to restart the job or just cancel it.
389 setPrintJobState(printJob.getId(), PrintJobInfo.STATE_FAILED,
390 getString(R.string.no_connection_to_printer));
395 if (!mPrintJobs.isEmpty()) {
396 // Update the notification.
397 mNotificationController.onUpdateNotifications(mPrintJobs);
400 // Delete the orphan files.
401 if (fileForJobMap != null) {
402 final int orphanFileCount = fileForJobMap.size();
403 for (int i = 0; i < orphanFileCount; i++) {
404 File file = fileForJobMap.valueAt(i);
410 public void checkAllPrintJobsHandled() {
411 synchronized (mLock) {
412 if (!hasActivePrintJobsLocked()) {
413 notifyOnAllPrintJobsHandled();
418 public void writePrintJobData(final ParcelFileDescriptor fd, final PrintJobId printJobId) {
419 final PrintJobInfo printJob;
420 synchronized (mLock) {
421 printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
423 new AsyncTask<Void, Void, Void>() {
425 protected Void doInBackground(Void... params) {
426 FileInputStream in = null;
427 FileOutputStream out = null;
429 if (printJob != null) {
430 File file = generateFileForPrintJob(PrintSpoolerService.this, printJobId);
431 in = new FileInputStream(file);
432 out = new FileOutputStream(fd.getFileDescriptor());
434 final byte[] buffer = new byte[8192];
436 final int readByteCount = in.read(buffer);
437 if (readByteCount < 0) {
440 out.write(buffer, 0, readByteCount);
442 } catch (FileNotFoundException fnfe) {
443 Log.e(LOG_TAG, "Error writing print job data!", fnfe);
444 } catch (IOException ioe) {
445 Log.e(LOG_TAG, "Error writing print job data!", ioe);
447 IoUtils.closeQuietly(in);
448 IoUtils.closeQuietly(out);
449 IoUtils.closeQuietly(fd);
451 Log.i(LOG_TAG, "[END WRITE]");
454 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
457 public static File generateFileForPrintJob(Context context, PrintJobId printJobId) {
458 return new File(context.getFilesDir(), PRINT_JOB_FILE_PREFIX
459 + printJobId.flattenToString() + "." + PRINT_FILE_EXTENSION);
462 private void addPrintJobLocked(PrintJobInfo printJob) {
463 mPrintJobs.add(printJob);
464 if (DEBUG_PRINT_JOB_LIFECYCLE) {
465 Slog.i(LOG_TAG, "[ADD] " + printJob);
469 private void removeObsoletePrintJobs() {
470 synchronized (mLock) {
471 boolean persistState = false;
472 final int printJobCount = mPrintJobs.size();
473 for (int i = printJobCount - 1; i >= 0; i--) {
474 PrintJobInfo printJob = mPrintJobs.get(i);
475 if (isObsoleteState(printJob.getState())) {
476 mPrintJobs.remove(i);
477 if (DEBUG_PRINT_JOB_LIFECYCLE) {
478 Slog.i(LOG_TAG, "[REMOVE] " + printJob.getId().flattenToString());
480 removePrintJobFileLocked(printJob.getId());
485 mPersistanceManager.writeStateLocked();
490 private void removePrintJobFileLocked(PrintJobId printJobId) {
491 File file = generateFileForPrintJob(PrintSpoolerService.this, printJobId);
494 if (DEBUG_PRINT_JOB_LIFECYCLE) {
495 Slog.i(LOG_TAG, "[REMOVE FILE FOR] " + printJobId);
500 public boolean setPrintJobState(PrintJobId printJobId, int state, String error) {
501 boolean success = false;
503 synchronized (mLock) {
504 PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
505 if (printJob != null) {
506 final int oldState = printJob.getState();
507 if (oldState == state) {
513 printJob.setState(state);
514 printJob.setStatus(error);
515 printJob.setCancelling(false);
517 if (DEBUG_PRINT_JOB_LIFECYCLE) {
518 Slog.i(LOG_TAG, "[STATE CHANGED] " + printJob);
521 MetricsLogger.histogram(this, "print_job_state", state);
523 case PrintJobInfo.STATE_COMPLETED:
524 case PrintJobInfo.STATE_CANCELED:
525 mPrintJobs.remove(printJob);
526 removePrintJobFileLocked(printJob.getId());
529 case PrintJobInfo.STATE_FAILED: {
530 PrinterId printerId = printJob.getPrinterId();
531 if (printerId != null) {
532 ComponentName service = printerId.getServiceName();
533 if (!hasActivePrintJobsForServiceLocked(service)) {
534 sendOnAllPrintJobsForServiceHandled(service);
539 case PrintJobInfo.STATE_QUEUED: {
540 sendOnPrintJobQueued(new PrintJobInfo(printJob));
544 if (shouldPersistPrintJob(printJob)) {
545 mPersistanceManager.writeStateLocked();
548 if (!hasActivePrintJobsLocked()) {
549 notifyOnAllPrintJobsHandled();
552 Message message = mHandlerCaller.obtainMessageO(
553 HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED,
555 mHandlerCaller.executeOrSendMessage(message);
557 mNotificationController.onUpdateNotifications(mPrintJobs);
565 * Set the progress for a print job.
567 * @param printJobId ID of the print job to update
568 * @param progress the new progress
570 public void setProgress(@NonNull PrintJobId printJobId,
571 @FloatRange(from=0.0, to=1.0) float progress) {
572 synchronized (mLock) {
573 getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY).setProgress(progress);
575 mNotificationController.onUpdateNotifications(mPrintJobs);
580 * Set the status for a print job.
582 * @param printJobId ID of the print job to update
583 * @param status the new status
585 public void setStatus(@NonNull PrintJobId printJobId, @Nullable CharSequence status) {
586 synchronized (mLock) {
587 getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY).setStatus(status);
589 mNotificationController.onUpdateNotifications(mPrintJobs);
594 * Set the status for a print job.
596 * @param printJobId ID of the print job to update
597 * @param status the new status as a string resource
598 * @param appPackageName app package the resource belongs to
600 public void setStatus(@NonNull PrintJobId printJobId, @StringRes int status,
601 @Nullable CharSequence appPackageName) {
602 synchronized (mLock) {
603 getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY).setStatus(status, appPackageName);
605 mNotificationController.onUpdateNotifications(mPrintJobs);
609 public boolean hasActivePrintJobsLocked() {
610 final int printJobCount = mPrintJobs.size();
611 for (int i = 0; i < printJobCount; i++) {
612 PrintJobInfo printJob = mPrintJobs.get(i);
613 if (isActiveState(printJob.getState())) {
620 public boolean hasActivePrintJobsForServiceLocked(ComponentName service) {
621 final int printJobCount = mPrintJobs.size();
622 for (int i = 0; i < printJobCount; i++) {
623 PrintJobInfo printJob = mPrintJobs.get(i);
624 if (isActiveState(printJob.getState()) && printJob.getPrinterId() != null
625 && printJob.getPrinterId().getServiceName().equals(service)) {
632 private boolean isObsoleteState(int printJobState) {
633 return (isTerminalState(printJobState)
634 || printJobState == PrintJobInfo.STATE_QUEUED);
637 private boolean isScheduledState(int printJobState) {
638 return printJobState == PrintJobInfo.STATE_QUEUED
639 || printJobState == PrintJobInfo.STATE_STARTED
640 || printJobState == PrintJobInfo.STATE_BLOCKED;
643 private boolean isActiveState(int printJobState) {
644 return printJobState == PrintJobInfo.STATE_CREATED
645 || printJobState == PrintJobInfo.STATE_QUEUED
646 || printJobState == PrintJobInfo.STATE_STARTED
647 || printJobState == PrintJobInfo.STATE_BLOCKED;
650 private boolean isTerminalState(int printJobState) {
651 return printJobState == PrintJobInfo.STATE_COMPLETED
652 || printJobState == PrintJobInfo.STATE_CANCELED;
655 public boolean setPrintJobTag(PrintJobId printJobId, String tag) {
656 synchronized (mLock) {
657 PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
658 if (printJob != null) {
659 String printJobTag = printJob.getTag();
660 if (printJobTag == null) {
664 } else if (printJobTag.equals(tag)) {
667 printJob.setTag(tag);
668 if (shouldPersistPrintJob(printJob)) {
669 mPersistanceManager.writeStateLocked();
677 public void setPrintJobCancelling(PrintJobId printJobId, boolean cancelling) {
678 synchronized (mLock) {
679 PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
680 if (printJob != null) {
681 printJob.setCancelling(cancelling);
682 if (shouldPersistPrintJob(printJob)) {
683 mPersistanceManager.writeStateLocked();
685 mNotificationController.onUpdateNotifications(mPrintJobs);
687 Message message = mHandlerCaller.obtainMessageO(
688 HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED,
690 mHandlerCaller.executeOrSendMessage(message);
695 public void updatePrintJobUserConfigurableOptionsNoPersistence(PrintJobInfo printJob) {
696 synchronized (mLock) {
697 final int printJobCount = mPrintJobs.size();
698 for (int i = 0; i < printJobCount; i++) {
699 PrintJobInfo cachedPrintJob = mPrintJobs.get(i);
700 if (cachedPrintJob.getId().equals(printJob.getId())) {
701 cachedPrintJob.setPrinterId(printJob.getPrinterId());
702 cachedPrintJob.setPrinterName(printJob.getPrinterName());
703 cachedPrintJob.setCopies(printJob.getCopies());
704 cachedPrintJob.setDocumentInfo(printJob.getDocumentInfo());
705 cachedPrintJob.setPages(printJob.getPages());
706 cachedPrintJob.setAttributes(printJob.getAttributes());
707 cachedPrintJob.setAdvancedOptions(printJob.getAdvancedOptions());
711 throw new IllegalArgumentException("No print job with id:" + printJob.getId());
715 private boolean shouldPersistPrintJob(PrintJobInfo printJob) {
716 return printJob.getState() >= PrintJobInfo.STATE_QUEUED;
719 private void notifyOnAllPrintJobsHandled() {
720 // This has to run on the tread that is persisting the current state
721 // since this call may result in the system unbinding from the spooler
722 // and as a result the spooler process may get killed before the write
724 new AsyncTask<Void, Void, Void>() {
726 protected Void doInBackground(Void... params) {
727 sendOnAllPrintJobsHandled();
730 }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
734 * Handle that a custom icon for a printer was loaded.
736 * @param printerId the id of the printer the icon belongs to
737 * @param icon the icon that was loaded
738 * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon()
740 public void onCustomPrinterIconLoaded(PrinterId printerId, Icon icon) {
741 mCustomIconCache.onCustomPrinterIconLoaded(printerId, icon);
745 * Get the custom icon for a printer. If the icon is not cached, the icon is
746 * requested asynchronously. Once it is available the printer is updated.
748 * @param printerId the id of the printer the icon should be loaded for
749 * @return the custom icon to be used for the printer or null if the icon is
751 * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon()
753 public Icon getCustomPrinterIcon(PrinterId printerId) {
754 return mCustomIconCache.getIcon(printerId);
758 * Clear the custom printer icon cache.
760 public void clearCustomPrinterIconCache() {
761 mCustomIconCache.clear();
764 private final class PersistenceManager {
765 private static final String PERSIST_FILE_NAME = "print_spooler_state.xml";
767 private static final String TAG_SPOOLER = "spooler";
768 private static final String TAG_JOB = "job";
770 private static final String TAG_PRINTER_ID = "printerId";
771 private static final String TAG_PAGE_RANGE = "pageRange";
772 private static final String TAG_ATTRIBUTES = "attributes";
773 private static final String TAG_DOCUMENT_INFO = "documentInfo";
775 private static final String ATTR_ID = "id";
776 private static final String ATTR_LABEL = "label";
777 private static final String ATTR_LABEL_RES_ID = "labelResId";
778 private static final String ATTR_PACKAGE_NAME = "packageName";
779 private static final String ATTR_STATE = "state";
780 private static final String ATTR_APP_ID = "appId";
781 private static final String ATTR_TAG = "tag";
782 private static final String ATTR_CREATION_TIME = "creationTime";
783 private static final String ATTR_COPIES = "copies";
784 private static final String ATTR_PRINTER_NAME = "printerName";
785 private static final String ATTR_STATE_REASON = "stateReason";
786 private static final String ATTR_STATUS = "status";
787 private static final String ATTR_PROGRESS = "progress";
788 private static final String ATTR_CANCELLING = "cancelling";
790 private static final String TAG_ADVANCED_OPTIONS = "advancedOptions";
791 private static final String TAG_ADVANCED_OPTION = "advancedOption";
792 private static final String ATTR_KEY = "key";
793 private static final String ATTR_TYPE = "type";
794 private static final String ATTR_VALUE = "value";
795 private static final String TYPE_STRING = "string";
796 private static final String TYPE_INT = "int";
798 private static final String TAG_MEDIA_SIZE = "mediaSize";
799 private static final String TAG_RESOLUTION = "resolution";
800 private static final String TAG_MARGINS = "margins";
802 private static final String ATTR_COLOR_MODE = "colorMode";
803 private static final String ATTR_DUPLEX_MODE = "duplexMode";
805 private static final String ATTR_LOCAL_ID = "localId";
806 private static final String ATTR_SERVICE_NAME = "serviceName";
808 private static final String ATTR_WIDTH_MILS = "widthMils";
809 private static final String ATTR_HEIGHT_MILS = "heightMils";
811 private static final String ATTR_HORIZONTAL_DPI = "horizontalDip";
812 private static final String ATTR_VERTICAL_DPI = "verticalDpi";
814 private static final String ATTR_LEFT_MILS = "leftMils";
815 private static final String ATTR_TOP_MILS = "topMils";
816 private static final String ATTR_RIGHT_MILS = "rightMils";
817 private static final String ATTR_BOTTOM_MILS = "bottomMils";
819 private static final String ATTR_START = "start";
820 private static final String ATTR_END = "end";
822 private static final String ATTR_NAME = "name";
823 private static final String ATTR_PAGE_COUNT = "pageCount";
824 private static final String ATTR_CONTENT_TYPE = "contentType";
825 private static final String ATTR_DATA_SIZE = "dataSize";
827 private final AtomicFile mStatePersistFile;
829 private boolean mWriteStateScheduled;
831 private PersistenceManager() {
832 mStatePersistFile = new AtomicFile(new File(getFilesDir(),
836 public void writeStateLocked() {
837 if (!PERSISTENCE_MANAGER_ENABLED) {
840 if (mWriteStateScheduled) {
843 mWriteStateScheduled = true;
844 new AsyncTask<Void, Void, Void>() {
846 protected Void doInBackground(Void... params) {
847 synchronized (mLock) {
848 mWriteStateScheduled = false;
849 doWriteStateLocked();
853 }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
856 private void doWriteStateLocked() {
857 if (DEBUG_PERSISTENCE) {
858 Log.i(LOG_TAG, "[PERSIST START]");
860 FileOutputStream out = null;
862 out = mStatePersistFile.startWrite();
864 XmlSerializer serializer = new FastXmlSerializer();
865 serializer.setOutput(out, StandardCharsets.UTF_8.name());
866 serializer.startDocument(null, true);
867 serializer.startTag(null, TAG_SPOOLER);
869 List<PrintJobInfo> printJobs = mPrintJobs;
871 final int printJobCount = printJobs.size();
872 for (int j = 0; j < printJobCount; j++) {
873 PrintJobInfo printJob = printJobs.get(j);
875 if (!shouldPersistPrintJob(printJob)) {
879 serializer.startTag(null, TAG_JOB);
881 serializer.attribute(null, ATTR_ID, printJob.getId().flattenToString());
882 serializer.attribute(null, ATTR_LABEL, printJob.getLabel().toString());
883 serializer.attribute(null, ATTR_STATE, String.valueOf(printJob.getState()));
884 serializer.attribute(null, ATTR_APP_ID, String.valueOf(printJob.getAppId()));
885 String tag = printJob.getTag();
887 serializer.attribute(null, ATTR_TAG, tag);
889 serializer.attribute(null, ATTR_CREATION_TIME, String.valueOf(
890 printJob.getCreationTime()));
891 serializer.attribute(null, ATTR_COPIES, String.valueOf(printJob.getCopies()));
892 String printerName = printJob.getPrinterName();
893 if (!TextUtils.isEmpty(printerName)) {
894 serializer.attribute(null, ATTR_PRINTER_NAME, printerName);
896 serializer.attribute(null, ATTR_CANCELLING, String.valueOf(
897 printJob.isCancelling()));
899 float progress = printJob.getProgress();
900 if (progress != Float.NaN) {
901 serializer.attribute(null, ATTR_PROGRESS, String.valueOf(progress));
904 CharSequence status = printJob.getStatus(getPackageManager());
905 if (!TextUtils.isEmpty(status)) {
906 serializer.attribute(null, ATTR_STATUS, status.toString());
909 PrinterId printerId = printJob.getPrinterId();
910 if (printerId != null) {
911 serializer.startTag(null, TAG_PRINTER_ID);
912 serializer.attribute(null, ATTR_LOCAL_ID, printerId.getLocalId());
913 serializer.attribute(null, ATTR_SERVICE_NAME, printerId.getServiceName()
915 serializer.endTag(null, TAG_PRINTER_ID);
918 PageRange[] pages = printJob.getPages();
920 for (int i = 0; i < pages.length; i++) {
921 serializer.startTag(null, TAG_PAGE_RANGE);
922 serializer.attribute(null, ATTR_START, String.valueOf(
923 pages[i].getStart()));
924 serializer.attribute(null, ATTR_END, String.valueOf(
926 serializer.endTag(null, TAG_PAGE_RANGE);
930 PrintAttributes attributes = printJob.getAttributes();
931 if (attributes != null) {
932 serializer.startTag(null, TAG_ATTRIBUTES);
934 final int colorMode = attributes.getColorMode();
935 serializer.attribute(null, ATTR_COLOR_MODE,
936 String.valueOf(colorMode));
938 final int duplexMode = attributes.getDuplexMode();
939 serializer.attribute(null, ATTR_DUPLEX_MODE,
940 String.valueOf(duplexMode));
942 MediaSize mediaSize = attributes.getMediaSize();
943 if (mediaSize != null) {
944 serializer.startTag(null, TAG_MEDIA_SIZE);
945 serializer.attribute(null, ATTR_ID, mediaSize.getId());
946 serializer.attribute(null, ATTR_WIDTH_MILS, String.valueOf(
947 mediaSize.getWidthMils()));
948 serializer.attribute(null, ATTR_HEIGHT_MILS, String.valueOf(
949 mediaSize.getHeightMils()));
950 // We prefer to store only the package name and
951 // resource id and fallback to the label.
952 if (!TextUtils.isEmpty(mediaSize.mPackageName)
953 && mediaSize.mLabelResId > 0) {
954 serializer.attribute(null, ATTR_PACKAGE_NAME,
955 mediaSize.mPackageName);
956 serializer.attribute(null, ATTR_LABEL_RES_ID,
957 String.valueOf(mediaSize.mLabelResId));
959 serializer.attribute(null, ATTR_LABEL,
960 mediaSize.getLabel(getPackageManager()));
962 serializer.endTag(null, TAG_MEDIA_SIZE);
965 Resolution resolution = attributes.getResolution();
966 if (resolution != null) {
967 serializer.startTag(null, TAG_RESOLUTION);
968 serializer.attribute(null, ATTR_ID, resolution.getId());
969 serializer.attribute(null, ATTR_HORIZONTAL_DPI, String.valueOf(
970 resolution.getHorizontalDpi()));
971 serializer.attribute(null, ATTR_VERTICAL_DPI, String.valueOf(
972 resolution.getVerticalDpi()));
973 serializer.attribute(null, ATTR_LABEL,
974 resolution.getLabel());
975 serializer.endTag(null, TAG_RESOLUTION);
978 Margins margins = attributes.getMinMargins();
979 if (margins != null) {
980 serializer.startTag(null, TAG_MARGINS);
981 serializer.attribute(null, ATTR_LEFT_MILS, String.valueOf(
982 margins.getLeftMils()));
983 serializer.attribute(null, ATTR_TOP_MILS, String.valueOf(
984 margins.getTopMils()));
985 serializer.attribute(null, ATTR_RIGHT_MILS, String.valueOf(
986 margins.getRightMils()));
987 serializer.attribute(null, ATTR_BOTTOM_MILS, String.valueOf(
988 margins.getBottomMils()));
989 serializer.endTag(null, TAG_MARGINS);
992 serializer.endTag(null, TAG_ATTRIBUTES);
995 PrintDocumentInfo documentInfo = printJob.getDocumentInfo();
996 if (documentInfo != null) {
997 serializer.startTag(null, TAG_DOCUMENT_INFO);
998 serializer.attribute(null, ATTR_NAME, documentInfo.getName());
999 serializer.attribute(null, ATTR_CONTENT_TYPE, String.valueOf(
1000 documentInfo.getContentType()));
1001 serializer.attribute(null, ATTR_PAGE_COUNT, String.valueOf(
1002 documentInfo.getPageCount()));
1003 serializer.attribute(null, ATTR_DATA_SIZE, String.valueOf(
1004 documentInfo.getDataSize()));
1005 serializer.endTag(null, TAG_DOCUMENT_INFO);
1008 Bundle advancedOptions = printJob.getAdvancedOptions();
1009 if (advancedOptions != null) {
1010 serializer.startTag(null, TAG_ADVANCED_OPTIONS);
1011 for (String key : advancedOptions.keySet()) {
1012 Object value = advancedOptions.get(key);
1013 if (value instanceof String) {
1014 String stringValue = (String) value;
1015 serializer.startTag(null, TAG_ADVANCED_OPTION);
1016 serializer.attribute(null, ATTR_KEY, key);
1017 serializer.attribute(null, ATTR_TYPE, TYPE_STRING);
1018 serializer.attribute(null, ATTR_VALUE, stringValue);
1019 serializer.endTag(null, TAG_ADVANCED_OPTION);
1020 } else if (value instanceof Integer) {
1021 String intValue = Integer.toString((Integer) value);
1022 serializer.startTag(null, TAG_ADVANCED_OPTION);
1023 serializer.attribute(null, ATTR_KEY, key);
1024 serializer.attribute(null, ATTR_TYPE, TYPE_INT);
1025 serializer.attribute(null, ATTR_VALUE, intValue);
1026 serializer.endTag(null, TAG_ADVANCED_OPTION);
1029 serializer.endTag(null, TAG_ADVANCED_OPTIONS);
1032 serializer.endTag(null, TAG_JOB);
1034 if (DEBUG_PERSISTENCE) {
1035 Log.i(LOG_TAG, "[PERSISTED] " + printJob);
1039 serializer.endTag(null, TAG_SPOOLER);
1040 serializer.endDocument();
1041 mStatePersistFile.finishWrite(out);
1042 if (DEBUG_PERSISTENCE) {
1043 Log.i(LOG_TAG, "[PERSIST END]");
1045 } catch (IOException e) {
1046 Slog.w(LOG_TAG, "Failed to write state, restoring backup.", e);
1047 mStatePersistFile.failWrite(out);
1049 IoUtils.closeQuietly(out);
1053 public void readStateLocked() {
1054 if (!PERSISTENCE_MANAGER_ENABLED) {
1057 FileInputStream in = null;
1059 in = mStatePersistFile.openRead();
1060 } catch (FileNotFoundException e) {
1061 if (DEBUG_PERSISTENCE) {
1062 Log.d(LOG_TAG, "No existing print spooler state.");
1067 XmlPullParser parser = Xml.newPullParser();
1068 parser.setInput(in, StandardCharsets.UTF_8.name());
1070 } catch (IllegalStateException ise) {
1071 Slog.w(LOG_TAG, "Failed parsing ", ise);
1072 } catch (NullPointerException npe) {
1073 Slog.w(LOG_TAG, "Failed parsing ", npe);
1074 } catch (NumberFormatException nfe) {
1075 Slog.w(LOG_TAG, "Failed parsing ", nfe);
1076 } catch (XmlPullParserException xppe) {
1077 Slog.w(LOG_TAG, "Failed parsing ", xppe);
1078 } catch (IOException ioe) {
1079 Slog.w(LOG_TAG, "Failed parsing ", ioe);
1080 } catch (IndexOutOfBoundsException iobe) {
1081 Slog.w(LOG_TAG, "Failed parsing ", iobe);
1083 IoUtils.closeQuietly(in);
1087 private void parseState(XmlPullParser parser)
1088 throws IOException, XmlPullParserException {
1090 skipEmptyTextTags(parser);
1091 expect(parser, XmlPullParser.START_TAG, TAG_SPOOLER);
1094 while (parsePrintJob(parser)) {
1098 skipEmptyTextTags(parser);
1099 expect(parser, XmlPullParser.END_TAG, TAG_SPOOLER);
1102 private boolean parsePrintJob(XmlPullParser parser)
1103 throws IOException, XmlPullParserException {
1104 skipEmptyTextTags(parser);
1105 if (!accept(parser, XmlPullParser.START_TAG, TAG_JOB)) {
1109 PrintJobInfo printJob = new PrintJobInfo();
1111 PrintJobId printJobId = PrintJobId.unflattenFromString(
1112 parser.getAttributeValue(null, ATTR_ID));
1113 printJob.setId(printJobId);
1114 String label = parser.getAttributeValue(null, ATTR_LABEL);
1115 printJob.setLabel(label);
1116 final int state = Integer.parseInt(parser.getAttributeValue(null, ATTR_STATE));
1117 printJob.setState(state);
1118 final int appId = Integer.parseInt(parser.getAttributeValue(null, ATTR_APP_ID));
1119 printJob.setAppId(appId);
1120 String tag = parser.getAttributeValue(null, ATTR_TAG);
1121 printJob.setTag(tag);
1122 String creationTime = parser.getAttributeValue(null, ATTR_CREATION_TIME);
1123 printJob.setCreationTime(Long.parseLong(creationTime));
1124 String copies = parser.getAttributeValue(null, ATTR_COPIES);
1125 printJob.setCopies(Integer.parseInt(copies));
1126 String printerName = parser.getAttributeValue(null, ATTR_PRINTER_NAME);
1127 printJob.setPrinterName(printerName);
1129 String progressString = parser.getAttributeValue(null, ATTR_PROGRESS);
1130 if (progressString != null) {
1131 float progress = Float.parseFloat(progressString);
1133 if (progress != -1) {
1134 printJob.setProgress(progress);
1138 CharSequence status = parser.getAttributeValue(null, ATTR_STATUS);
1139 printJob.setStatus(status);
1141 // stateReason is deprecated, but might be used by old print jobs
1142 String stateReason = parser.getAttributeValue(null, ATTR_STATE_REASON);
1143 if (stateReason != null) {
1144 printJob.setStatus(stateReason);
1147 String cancelling = parser.getAttributeValue(null, ATTR_CANCELLING);
1148 printJob.setCancelling(!TextUtils.isEmpty(cancelling)
1149 ? Boolean.parseBoolean(cancelling) : false);
1153 skipEmptyTextTags(parser);
1154 if (accept(parser, XmlPullParser.START_TAG, TAG_PRINTER_ID)) {
1155 String localId = parser.getAttributeValue(null, ATTR_LOCAL_ID);
1156 ComponentName service = ComponentName.unflattenFromString(parser.getAttributeValue(
1157 null, ATTR_SERVICE_NAME));
1158 printJob.setPrinterId(new PrinterId(service, localId));
1160 skipEmptyTextTags(parser);
1161 expect(parser, XmlPullParser.END_TAG, TAG_PRINTER_ID);
1165 skipEmptyTextTags(parser);
1166 List<PageRange> pageRanges = null;
1167 while (accept(parser, XmlPullParser.START_TAG, TAG_PAGE_RANGE)) {
1168 final int start = Integer.parseInt(parser.getAttributeValue(null, ATTR_START));
1169 final int end = Integer.parseInt(parser.getAttributeValue(null, ATTR_END));
1170 PageRange pageRange = new PageRange(start, end);
1171 if (pageRanges == null) {
1172 pageRanges = new ArrayList<PageRange>();
1174 pageRanges.add(pageRange);
1176 skipEmptyTextTags(parser);
1177 expect(parser, XmlPullParser.END_TAG, TAG_PAGE_RANGE);
1179 skipEmptyTextTags(parser);
1181 if (pageRanges != null) {
1182 PageRange[] pageRangesArray = new PageRange[pageRanges.size()];
1183 pageRanges.toArray(pageRangesArray);
1184 printJob.setPages(pageRangesArray);
1187 skipEmptyTextTags(parser);
1188 if (accept(parser, XmlPullParser.START_TAG, TAG_ATTRIBUTES)) {
1190 PrintAttributes.Builder builder = new PrintAttributes.Builder();
1192 String colorMode = parser.getAttributeValue(null, ATTR_COLOR_MODE);
1193 builder.setColorMode(Integer.parseInt(colorMode));
1195 String duplexMode = parser.getAttributeValue(null, ATTR_DUPLEX_MODE);
1196 // Duplex mode was added later, so null check is needed.
1197 if (duplexMode != null) {
1198 builder.setDuplexMode(Integer.parseInt(duplexMode));
1203 skipEmptyTextTags(parser);
1204 if (accept(parser, XmlPullParser.START_TAG, TAG_MEDIA_SIZE)) {
1205 String id = parser.getAttributeValue(null, ATTR_ID);
1206 label = parser.getAttributeValue(null, ATTR_LABEL);
1207 final int widthMils = Integer.parseInt(parser.getAttributeValue(null,
1209 final int heightMils = Integer.parseInt(parser.getAttributeValue(null,
1211 String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
1212 String labelResIdString = parser.getAttributeValue(null, ATTR_LABEL_RES_ID);
1213 final int labelResId = (labelResIdString != null)
1214 ? Integer.parseInt(labelResIdString) : 0;
1215 label = parser.getAttributeValue(null, ATTR_LABEL);
1216 MediaSize mediaSize = new MediaSize(id, label, packageName,
1217 widthMils, heightMils, labelResId);
1218 builder.setMediaSize(mediaSize);
1220 skipEmptyTextTags(parser);
1221 expect(parser, XmlPullParser.END_TAG, TAG_MEDIA_SIZE);
1225 skipEmptyTextTags(parser);
1226 if (accept(parser, XmlPullParser.START_TAG, TAG_RESOLUTION)) {
1227 String id = parser.getAttributeValue(null, ATTR_ID);
1228 label = parser.getAttributeValue(null, ATTR_LABEL);
1229 final int horizontalDpi = Integer.parseInt(parser.getAttributeValue(null,
1230 ATTR_HORIZONTAL_DPI));
1231 final int verticalDpi = Integer.parseInt(parser.getAttributeValue(null,
1232 ATTR_VERTICAL_DPI));
1233 Resolution resolution = new Resolution(id, label, horizontalDpi, verticalDpi);
1234 builder.setResolution(resolution);
1236 skipEmptyTextTags(parser);
1237 expect(parser, XmlPullParser.END_TAG, TAG_RESOLUTION);
1241 skipEmptyTextTags(parser);
1242 if (accept(parser, XmlPullParser.START_TAG, TAG_MARGINS)) {
1243 final int leftMils = Integer.parseInt(parser.getAttributeValue(null,
1245 final int topMils = Integer.parseInt(parser.getAttributeValue(null,
1247 final int rightMils = Integer.parseInt(parser.getAttributeValue(null,
1249 final int bottomMils = Integer.parseInt(parser.getAttributeValue(null,
1251 Margins margins = new Margins(leftMils, topMils, rightMils, bottomMils);
1252 builder.setMinMargins(margins);
1254 skipEmptyTextTags(parser);
1255 expect(parser, XmlPullParser.END_TAG, TAG_MARGINS);
1259 printJob.setAttributes(builder.build());
1261 skipEmptyTextTags(parser);
1262 expect(parser, XmlPullParser.END_TAG, TAG_ATTRIBUTES);
1266 skipEmptyTextTags(parser);
1267 if (accept(parser, XmlPullParser.START_TAG, TAG_DOCUMENT_INFO)) {
1268 String name = parser.getAttributeValue(null, ATTR_NAME);
1269 final int pageCount = Integer.parseInt(parser.getAttributeValue(null,
1271 final int contentType = Integer.parseInt(parser.getAttributeValue(null,
1272 ATTR_CONTENT_TYPE));
1273 final int dataSize = Integer.parseInt(parser.getAttributeValue(null,
1275 PrintDocumentInfo info = new PrintDocumentInfo.Builder(name)
1276 .setPageCount(pageCount)
1277 .setContentType(contentType).build();
1278 printJob.setDocumentInfo(info);
1279 info.setDataSize(dataSize);
1281 skipEmptyTextTags(parser);
1282 expect(parser, XmlPullParser.END_TAG, TAG_DOCUMENT_INFO);
1286 skipEmptyTextTags(parser);
1287 if (accept(parser, XmlPullParser.START_TAG, TAG_ADVANCED_OPTIONS)) {
1289 skipEmptyTextTags(parser);
1290 Bundle advancedOptions = new Bundle();
1291 while (accept(parser, XmlPullParser.START_TAG, TAG_ADVANCED_OPTION)) {
1292 String key = parser.getAttributeValue(null, ATTR_KEY);
1293 String value = parser.getAttributeValue(null, ATTR_VALUE);
1294 String type = parser.getAttributeValue(null, ATTR_TYPE);
1295 if (TYPE_STRING.equals(type)) {
1296 advancedOptions.putString(key, value);
1297 } else if (TYPE_INT.equals(type)) {
1298 advancedOptions.putInt(key, Integer.valueOf(value));
1301 skipEmptyTextTags(parser);
1302 expect(parser, XmlPullParser.END_TAG, TAG_ADVANCED_OPTION);
1304 skipEmptyTextTags(parser);
1306 printJob.setAdvancedOptions(advancedOptions);
1307 skipEmptyTextTags(parser);
1308 expect(parser, XmlPullParser.END_TAG, TAG_ADVANCED_OPTIONS);
1312 mPrintJobs.add(printJob);
1314 if (DEBUG_PERSISTENCE) {
1315 Log.i(LOG_TAG, "[RESTORED] " + printJob);
1318 skipEmptyTextTags(parser);
1319 expect(parser, XmlPullParser.END_TAG, TAG_JOB);
1324 private void expect(XmlPullParser parser, int type, String tag)
1325 throws XmlPullParserException {
1326 if (!accept(parser, type, tag)) {
1327 throw new XmlPullParserException("Exepected event: " + type
1328 + " and tag: " + tag + " but got event: " + parser.getEventType()
1329 + " and tag:" + parser.getName());
1333 private void skipEmptyTextTags(XmlPullParser parser)
1334 throws IOException, XmlPullParserException {
1335 while (accept(parser, XmlPullParser.TEXT, null)
1336 && "\n".equals(parser.getText())) {
1341 private boolean accept(XmlPullParser parser, int type, String tag)
1342 throws XmlPullParserException {
1343 if (parser.getEventType() != type) {
1347 if (!tag.equals(parser.getName())) {
1350 } else if (parser.getName() != null) {
1357 public final class PrintSpooler extends IPrintSpooler.Stub {
1359 public void getPrintJobInfos(IPrintSpoolerCallbacks callback,
1360 ComponentName componentName, int state, int appId, int sequence)
1361 throws RemoteException {
1362 List<PrintJobInfo> printJobs = null;
1364 printJobs = PrintSpoolerService.this.getPrintJobInfos(
1365 componentName, state, appId);
1367 callback.onGetPrintJobInfosResult(printJobs, sequence);
1372 public void getPrintJobInfo(PrintJobId printJobId, IPrintSpoolerCallbacks callback,
1373 int appId, int sequence) throws RemoteException {
1374 PrintJobInfo printJob = null;
1376 printJob = PrintSpoolerService.this.getPrintJobInfo(printJobId, appId);
1378 callback.onGetPrintJobInfoResult(printJob, sequence);
1383 public void createPrintJob(PrintJobInfo printJob) {
1384 PrintSpoolerService.this.createPrintJob(printJob);
1388 public void setPrintJobState(PrintJobId printJobId, int state, String error,
1389 IPrintSpoolerCallbacks callback, int sequece) throws RemoteException {
1390 boolean success = false;
1392 success = PrintSpoolerService.this.setPrintJobState(
1393 printJobId, state, error);
1395 callback.onSetPrintJobStateResult(success, sequece);
1400 public void setPrintJobTag(PrintJobId printJobId, String tag,
1401 IPrintSpoolerCallbacks callback, int sequece) throws RemoteException {
1402 boolean success = false;
1404 success = PrintSpoolerService.this.setPrintJobTag(printJobId, tag);
1406 callback.onSetPrintJobTagResult(success, sequece);
1411 public void writePrintJobData(ParcelFileDescriptor fd, PrintJobId printJobId) {
1412 PrintSpoolerService.this.writePrintJobData(fd, printJobId);
1416 public void setClient(IPrintSpoolerClient client) {
1417 Message message = mHandlerCaller.obtainMessageO(
1418 HandlerCallerCallback.MSG_SET_CLIENT, client);
1419 mHandlerCaller.executeOrSendMessage(message);
1423 public void removeObsoletePrintJobs() {
1424 PrintSpoolerService.this.removeObsoletePrintJobs();
1428 protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
1429 PrintSpoolerService.this.dump(fd, writer, args);
1433 public void setPrintJobCancelling(PrintJobId printJobId, boolean cancelling) {
1434 PrintSpoolerService.this.setPrintJobCancelling(printJobId, cancelling);
1438 public void pruneApprovedPrintServices(List<ComponentName> servicesToKeep) {
1439 (new ApprovedPrintServices(PrintSpoolerService.this))
1440 .pruneApprovedServices(servicesToKeep);
1444 public void setProgress(@NonNull PrintJobId printJobId,
1445 @FloatRange(from=0.0, to=1.0) float progress) throws RemoteException {
1446 PrintSpoolerService.this.setProgress(printJobId, progress);
1450 public void setStatus(@NonNull PrintJobId printJobId,
1451 @Nullable CharSequence status) throws RemoteException {
1452 PrintSpoolerService.this.setStatus(printJobId, status);
1456 public void setStatusRes(@NonNull PrintJobId printJobId, @StringRes int status,
1457 @NonNull CharSequence appPackageName) throws RemoteException {
1458 PrintSpoolerService.this.setStatus(printJobId, status, appPackageName);
1462 public PrintSpoolerService getService() {
1463 return PrintSpoolerService.this;
1467 public void onCustomPrinterIconLoaded(PrinterId printerId, Icon icon,
1468 IPrintSpoolerCallbacks callbacks, int sequence)
1469 throws RemoteException {
1471 PrintSpoolerService.this.onCustomPrinterIconLoaded(printerId, icon);
1473 callbacks.onCustomPrinterIconCached(sequence);
1478 public void getCustomPrinterIcon(PrinterId printerId, IPrintSpoolerCallbacks callbacks,
1479 int sequence) throws RemoteException {
1482 icon = PrintSpoolerService.this.getCustomPrinterIcon(printerId);
1484 callbacks.onGetCustomPrinterIconResult(icon, sequence);
1489 public void clearCustomPrinterIconCache(IPrintSpoolerCallbacks callbacks,
1490 int sequence) throws RemoteException {
1492 PrintSpoolerService.this.clearCustomPrinterIconCache();
1494 callbacks.customPrinterIconCacheCleared(sequence);