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