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