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