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