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