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