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