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.server.print; 18 19 import android.annotation.FloatRange; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.StringRes; 23 import android.content.ComponentName; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.ServiceConnection; 27 import android.content.pm.ParceledListSlice; 28 import android.graphics.drawable.Icon; 29 import android.os.Binder; 30 import android.os.Handler; 31 import android.os.IBinder; 32 import android.os.IBinder.DeathRecipient; 33 import android.os.Looper; 34 import android.os.Message; 35 import android.os.ParcelFileDescriptor; 36 import android.os.RemoteException; 37 import android.os.UserHandle; 38 import android.print.PrintJobId; 39 import android.print.PrintJobInfo; 40 import android.print.PrintManager; 41 import android.print.PrinterId; 42 import android.print.PrinterInfo; 43 import android.printservice.IPrintService; 44 import android.printservice.IPrintServiceClient; 45 import android.util.Slog; 46 47 import java.io.PrintWriter; 48 import java.lang.ref.WeakReference; 49 import java.util.ArrayList; 50 import java.util.List; 51 52 /** 53 * This class represents a remote print service. It abstracts away the binding 54 * and unbinding from the remote implementation. Clients can call methods of 55 * this class without worrying about when and how to bind/unbind. 56 */ 57 final class RemotePrintService implements DeathRecipient { 58 59 private static final String LOG_TAG = "RemotePrintService"; 60 61 private static final boolean DEBUG = false; 62 63 private final Context mContext; 64 65 private final ComponentName mComponentName; 66 67 private final Intent mIntent; 68 69 private final RemotePrintSpooler mSpooler; 70 71 private final PrintServiceCallbacks mCallbacks; 72 73 private final int mUserId; 74 75 private final List<Runnable> mPendingCommands = new ArrayList<Runnable>(); 76 77 private final ServiceConnection mServiceConnection = new RemoteServiceConneciton(); 78 79 private final RemotePrintServiceClient mPrintServiceClient; 80 81 private final Handler mHandler; 82 83 private IPrintService mPrintService; 84 85 private boolean mBinding; 86 87 private boolean mDestroyed; 88 89 private boolean mHasActivePrintJobs; 90 91 private boolean mHasPrinterDiscoverySession; 92 93 private boolean mServiceDied; 94 95 private List<PrinterId> mDiscoveryPriorityList; 96 97 private List<PrinterId> mTrackedPrinterList; 98 99 public static interface PrintServiceCallbacks { 100 public void onPrintersAdded(List<PrinterInfo> printers); 101 public void onPrintersRemoved(List<PrinterId> printerIds); 102 public void onServiceDied(RemotePrintService service); 103 104 /** 105 * Handle that a custom icon for a printer was loaded. 106 * 107 * @param printerId the id of the printer the icon belongs to 108 * @param icon the icon that was loaded 109 * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon() 110 */ 111 public void onCustomPrinterIconLoaded(PrinterId printerId, Icon icon); 112 } 113 114 public RemotePrintService(Context context, ComponentName componentName, int userId, 115 RemotePrintSpooler spooler, PrintServiceCallbacks callbacks) { 116 mContext = context; 117 mCallbacks = callbacks; 118 mComponentName = componentName; 119 mIntent = new Intent().setComponent(mComponentName); 120 mUserId = userId; 121 mSpooler = spooler; 122 mHandler = new MyHandler(context.getMainLooper()); 123 mPrintServiceClient = new RemotePrintServiceClient(this); 124 } 125 126 public ComponentName getComponentName() { 127 return mComponentName; 128 } 129 130 public void destroy() { 131 mHandler.sendEmptyMessage(MyHandler.MSG_DESTROY); 132 } 133 134 private void handleDestroy() { 135 throwIfDestroyed(); 136 137 // Stop tracking printers. 138 stopTrackingAllPrinters(); 139 140 // Stop printer discovery. 141 if (mDiscoveryPriorityList != null) { 142 handleStopPrinterDiscovery(); 143 } 144 145 // Destroy the discovery session. 146 if (mHasPrinterDiscoverySession) { 147 handleDestroyPrinterDiscoverySession(); 148 } 149 150 // Unbind. 151 ensureUnbound(); 152 153 // Done 154 mDestroyed = true; 155 } 156 157 @Override 158 public void binderDied() { 159 mHandler.sendEmptyMessage(MyHandler.MSG_BINDER_DIED); 160 } 161 162 private void handleBinderDied() { 163 mPrintService.asBinder().unlinkToDeath(this, 0); 164 mPrintService = null; 165 mServiceDied = true; 166 mCallbacks.onServiceDied(this); 167 } 168 169 public void onAllPrintJobsHandled() { 170 mHandler.sendEmptyMessage(MyHandler.MSG_ON_ALL_PRINT_JOBS_HANDLED); 171 } 172 173 private void handleOnAllPrintJobsHandled() { 174 throwIfDestroyed(); 175 mHasActivePrintJobs = false; 176 if (!isBound()) { 177 // The service is dead and neither has active jobs nor discovery 178 // session, so ensure we are unbound since the service has no work. 179 if (mServiceDied && !mHasPrinterDiscoverySession) { 180 ensureUnbound(); 181 return; 182 } 183 ensureBound(); 184 mPendingCommands.add(new Runnable() { 185 @Override 186 public void run() { 187 handleOnAllPrintJobsHandled(); 188 } 189 }); 190 } else { 191 if (DEBUG) { 192 Slog.i(LOG_TAG, "[user: " + mUserId + "] onAllPrintJobsHandled()"); 193 } 194 // If the service has a printer discovery session 195 // created we should not disconnect from it just yet. 196 if (!mHasPrinterDiscoverySession) { 197 ensureUnbound(); 198 } 199 } 200 } 201 202 public void onRequestCancelPrintJob(PrintJobInfo printJob) { 203 mHandler.obtainMessage(MyHandler.MSG_ON_REQUEST_CANCEL_PRINT_JOB, 204 printJob).sendToTarget(); 205 } 206 207 private void handleRequestCancelPrintJob(final PrintJobInfo printJob) { 208 throwIfDestroyed(); 209 if (!isBound()) { 210 ensureBound(); 211 mPendingCommands.add(new Runnable() { 212 @Override 213 public void run() { 214 handleRequestCancelPrintJob(printJob); 215 } 216 }); 217 } else { 218 if (DEBUG) { 219 Slog.i(LOG_TAG, "[user: " + mUserId + "] requestCancelPrintJob()"); 220 } 221 try { 222 mPrintService.requestCancelPrintJob(printJob); 223 } catch (RemoteException re) { 224 Slog.e(LOG_TAG, "Error canceling a pring job.", re); 225 } 226 } 227 } 228 229 public void onPrintJobQueued(PrintJobInfo printJob) { 230 mHandler.obtainMessage(MyHandler.MSG_ON_PRINT_JOB_QUEUED, 231 printJob).sendToTarget(); 232 } 233 234 private void handleOnPrintJobQueued(final PrintJobInfo printJob) { 235 throwIfDestroyed(); 236 mHasActivePrintJobs = true; 237 if (!isBound()) { 238 ensureBound(); 239 mPendingCommands.add(new Runnable() { 240 @Override 241 public void run() { 242 handleOnPrintJobQueued(printJob); 243 } 244 }); 245 } else { 246 if (DEBUG) { 247 Slog.i(LOG_TAG, "[user: " + mUserId + "] onPrintJobQueued()"); 248 } 249 try { 250 mPrintService.onPrintJobQueued(printJob); 251 } catch (RemoteException re) { 252 Slog.e(LOG_TAG, "Error announcing queued pring job.", re); 253 } 254 } 255 } 256 257 public void createPrinterDiscoverySession() { 258 mHandler.sendEmptyMessage(MyHandler.MSG_CREATE_PRINTER_DISCOVERY_SESSION); 259 } 260 261 private void handleCreatePrinterDiscoverySession() { 262 throwIfDestroyed(); 263 mHasPrinterDiscoverySession = true; 264 if (!isBound()) { 265 ensureBound(); 266 mPendingCommands.add(new Runnable() { 267 @Override 268 public void run() { 269 handleCreatePrinterDiscoverySession(); 270 } 271 }); 272 } else { 273 if (DEBUG) { 274 Slog.i(LOG_TAG, "[user: " + mUserId + "] createPrinterDiscoverySession()"); 275 } 276 try { 277 mPrintService.createPrinterDiscoverySession(); 278 } catch (RemoteException re) { 279 Slog.e(LOG_TAG, "Error creating printer discovery session.", re); 280 } 281 } 282 } 283 284 public void destroyPrinterDiscoverySession() { 285 mHandler.sendEmptyMessage(MyHandler.MSG_DESTROY_PRINTER_DISCOVERY_SESSION); 286 } 287 288 private void handleDestroyPrinterDiscoverySession() { 289 throwIfDestroyed(); 290 mHasPrinterDiscoverySession = false; 291 if (!isBound()) { 292 // The service is dead and neither has active jobs nor discovery 293 // session, so ensure we are unbound since the service has no work. 294 if (mServiceDied && !mHasActivePrintJobs) { 295 ensureUnbound(); 296 return; 297 } 298 ensureBound(); 299 mPendingCommands.add(new Runnable() { 300 @Override 301 public void run() { 302 handleDestroyPrinterDiscoverySession(); 303 } 304 }); 305 } else { 306 if (DEBUG) { 307 Slog.i(LOG_TAG, "[user: " + mUserId + "] destroyPrinterDiscoverySession()"); 308 } 309 try { 310 mPrintService.destroyPrinterDiscoverySession(); 311 } catch (RemoteException re) { 312 Slog.e(LOG_TAG, "Error destroying printer dicovery session.", re); 313 } 314 // If the service has no print jobs and no active discovery 315 // session anymore we should disconnect from it. 316 if (!mHasActivePrintJobs) { 317 ensureUnbound(); 318 } 319 } 320 } 321 322 public void startPrinterDiscovery(List<PrinterId> priorityList) { 323 mHandler.obtainMessage(MyHandler.MSG_START_PRINTER_DISCOVERY, 324 priorityList).sendToTarget(); 325 } 326 327 private void handleStartPrinterDiscovery(final List<PrinterId> priorityList) { 328 throwIfDestroyed(); 329 // Take a note that we are doing discovery. 330 mDiscoveryPriorityList = new ArrayList<PrinterId>(); 331 if (priorityList != null) { 332 mDiscoveryPriorityList.addAll(priorityList); 333 } 334 if (!isBound()) { 335 ensureBound(); 336 mPendingCommands.add(new Runnable() { 337 @Override 338 public void run() { 339 handleStartPrinterDiscovery(priorityList); 340 } 341 }); 342 } else { 343 if (DEBUG) { 344 Slog.i(LOG_TAG, "[user: " + mUserId + "] startPrinterDiscovery()"); 345 } 346 try { 347 mPrintService.startPrinterDiscovery(priorityList); 348 } catch (RemoteException re) { 349 Slog.e(LOG_TAG, "Error starting printer dicovery.", re); 350 } 351 } 352 } 353 354 public void stopPrinterDiscovery() { 355 mHandler.sendEmptyMessage(MyHandler.MSG_STOP_PRINTER_DISCOVERY); 356 } 357 358 private void handleStopPrinterDiscovery() { 359 throwIfDestroyed(); 360 // We are not doing discovery anymore. 361 mDiscoveryPriorityList = null; 362 if (!isBound()) { 363 ensureBound(); 364 mPendingCommands.add(new Runnable() { 365 @Override 366 public void run() { 367 handleStopPrinterDiscovery(); 368 } 369 }); 370 } else { 371 if (DEBUG) { 372 Slog.i(LOG_TAG, "[user: " + mUserId + "] stopPrinterDiscovery()"); 373 } 374 375 // Stop tracking printers. 376 stopTrackingAllPrinters(); 377 378 try { 379 mPrintService.stopPrinterDiscovery(); 380 } catch (RemoteException re) { 381 Slog.e(LOG_TAG, "Error stopping printer discovery.", re); 382 } 383 } 384 } 385 386 public void validatePrinters(List<PrinterId> printerIds) { 387 mHandler.obtainMessage(MyHandler.MSG_VALIDATE_PRINTERS, 388 printerIds).sendToTarget(); 389 } 390 391 private void handleValidatePrinters(final List<PrinterId> printerIds) { 392 throwIfDestroyed(); 393 if (!isBound()) { 394 ensureBound(); 395 mPendingCommands.add(new Runnable() { 396 @Override 397 public void run() { 398 handleValidatePrinters(printerIds); 399 } 400 }); 401 } else { 402 if (DEBUG) { 403 Slog.i(LOG_TAG, "[user: " + mUserId + "] validatePrinters()"); 404 } 405 try { 406 mPrintService.validatePrinters(printerIds); 407 } catch (RemoteException re) { 408 Slog.e(LOG_TAG, "Error requesting printers validation.", re); 409 } 410 } 411 } 412 413 public void startPrinterStateTracking(@NonNull PrinterId printerId) { 414 mHandler.obtainMessage(MyHandler.MSG_START_PRINTER_STATE_TRACKING, 415 printerId).sendToTarget(); 416 } 417 418 /** 419 * Request the custom printer icon for a printer. 420 * 421 * @param printerId the id of the printer the icon should be loaded for 422 * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon() 423 */ 424 public void requestCustomPrinterIcon(@NonNull PrinterId printerId) { 425 try { 426 if (isBound()) { 427 mPrintService.requestCustomPrinterIcon(printerId); 428 } 429 } catch (RemoteException re) { 430 Slog.e(LOG_TAG, "Error requesting icon for " + printerId, re); 431 } 432 } 433 434 private void handleStartPrinterStateTracking(final @NonNull PrinterId printerId) { 435 throwIfDestroyed(); 436 // Take a note we are tracking the printer. 437 if (mTrackedPrinterList == null) { 438 mTrackedPrinterList = new ArrayList<PrinterId>(); 439 } 440 mTrackedPrinterList.add(printerId); 441 if (!isBound()) { 442 ensureBound(); 443 mPendingCommands.add(new Runnable() { 444 @Override 445 public void run() { 446 handleStartPrinterStateTracking(printerId); 447 } 448 }); 449 } else { 450 if (DEBUG) { 451 Slog.i(LOG_TAG, "[user: " + mUserId + "] startPrinterTracking()"); 452 } 453 try { 454 mPrintService.startPrinterStateTracking(printerId); 455 } catch (RemoteException re) { 456 Slog.e(LOG_TAG, "Error requesting start printer tracking.", re); 457 } 458 } 459 } 460 461 public void stopPrinterStateTracking(PrinterId printerId) { 462 mHandler.obtainMessage(MyHandler.MSG_STOP_PRINTER_STATE_TRACKING, 463 printerId).sendToTarget(); 464 } 465 466 private void handleStopPrinterStateTracking(final PrinterId printerId) { 467 throwIfDestroyed(); 468 // We are no longer tracking the printer. 469 if (mTrackedPrinterList == null || !mTrackedPrinterList.remove(printerId)) { 470 return; 471 } 472 if (mTrackedPrinterList.isEmpty()) { 473 mTrackedPrinterList = null; 474 } 475 if (!isBound()) { 476 ensureBound(); 477 mPendingCommands.add(new Runnable() { 478 @Override 479 public void run() { 480 handleStopPrinterStateTracking(printerId); 481 } 482 }); 483 } else { 484 if (DEBUG) { 485 Slog.i(LOG_TAG, "[user: " + mUserId + "] stopPrinterTracking()"); 486 } 487 try { 488 mPrintService.stopPrinterStateTracking(printerId); 489 } catch (RemoteException re) { 490 Slog.e(LOG_TAG, "Error requesting stop printer tracking.", re); 491 } 492 } 493 } 494 495 private void stopTrackingAllPrinters() { 496 if (mTrackedPrinterList == null) { 497 return; 498 } 499 final int trackedPrinterCount = mTrackedPrinterList.size(); 500 for (int i = trackedPrinterCount - 1; i >= 0; i--) { 501 PrinterId printerId = mTrackedPrinterList.get(i); 502 if (printerId.getServiceName().equals(mComponentName)) { 503 handleStopPrinterStateTracking(printerId); 504 } 505 } 506 } 507 508 public void dump(PrintWriter pw, String prefix) { 509 String tab = " "; 510 pw.append(prefix).append("service:").println(); 511 pw.append(prefix).append(tab).append("componentName=") 512 .append(mComponentName.flattenToString()).println(); 513 pw.append(prefix).append(tab).append("destroyed=") 514 .append(String.valueOf(mDestroyed)).println(); 515 pw.append(prefix).append(tab).append("bound=") 516 .append(String.valueOf(isBound())).println(); 517 pw.append(prefix).append(tab).append("hasDicoverySession=") 518 .append(String.valueOf(mHasPrinterDiscoverySession)).println(); 519 pw.append(prefix).append(tab).append("hasActivePrintJobs=") 520 .append(String.valueOf(mHasActivePrintJobs)).println(); 521 pw.append(prefix).append(tab).append("isDiscoveringPrinters=") 522 .append(String.valueOf(mDiscoveryPriorityList != null)).println(); 523 pw.append(prefix).append(tab).append("trackedPrinters=") 524 .append((mTrackedPrinterList != null) ? mTrackedPrinterList.toString() : "null"); 525 } 526 527 private boolean isBound() { 528 return mPrintService != null; 529 } 530 531 private void ensureBound() { 532 if (isBound() || mBinding) { 533 return; 534 } 535 if (DEBUG) { 536 Slog.i(LOG_TAG, "[user: " + mUserId + "] ensureBound()"); 537 } 538 mBinding = true; 539 mContext.bindServiceAsUser(mIntent, mServiceConnection, 540 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, 541 new UserHandle(mUserId)); 542 } 543 544 private void ensureUnbound() { 545 if (!isBound() && !mBinding) { 546 return; 547 } 548 if (DEBUG) { 549 Slog.i(LOG_TAG, "[user: " + mUserId + "] ensureUnbound()"); 550 } 551 mBinding = false; 552 mPendingCommands.clear(); 553 mHasActivePrintJobs = false; 554 mHasPrinterDiscoverySession = false; 555 mDiscoveryPriorityList = null; 556 mTrackedPrinterList = null; 557 if (isBound()) { 558 try { 559 mPrintService.setClient(null); 560 } catch (RemoteException re) { 561 /* ignore */ 562 } 563 mPrintService.asBinder().unlinkToDeath(this, 0); 564 mPrintService = null; 565 mContext.unbindService(mServiceConnection); 566 } 567 } 568 569 private void throwIfDestroyed() { 570 if (mDestroyed) { 571 throw new IllegalStateException("Cannot interact with a destroyed service"); 572 } 573 } 574 575 private class RemoteServiceConneciton implements ServiceConnection { 576 @Override 577 public void onServiceConnected(ComponentName name, IBinder service) { 578 if (mDestroyed || !mBinding) { 579 mContext.unbindService(mServiceConnection); 580 return; 581 } 582 mBinding = false; 583 mPrintService = IPrintService.Stub.asInterface(service); 584 try { 585 service.linkToDeath(RemotePrintService.this, 0); 586 } catch (RemoteException re) { 587 handleBinderDied(); 588 return; 589 } 590 try { 591 mPrintService.setClient(mPrintServiceClient); 592 } catch (RemoteException re) { 593 Slog.e(LOG_TAG, "Error setting client for: " + service, re); 594 handleBinderDied(); 595 return; 596 } 597 // If the service died and there is a discovery session, recreate it. 598 if (mServiceDied && mHasPrinterDiscoverySession) { 599 handleCreatePrinterDiscoverySession(); 600 } 601 // If the service died and there is discovery started, restart it. 602 if (mServiceDied && mDiscoveryPriorityList != null) { 603 handleStartPrinterDiscovery(mDiscoveryPriorityList); 604 } 605 // If the service died and printers were tracked, start tracking. 606 if (mServiceDied && mTrackedPrinterList != null) { 607 final int trackedPrinterCount = mTrackedPrinterList.size(); 608 for (int i = 0; i < trackedPrinterCount; i++) { 609 handleStartPrinterStateTracking(mTrackedPrinterList.get(i)); 610 } 611 } 612 // Finally, do all the pending work. 613 while (!mPendingCommands.isEmpty()) { 614 Runnable pendingCommand = mPendingCommands.remove(0); 615 pendingCommand.run(); 616 } 617 // We did a best effort to get to the last state if we crashed. 618 // If we do not have print jobs and no discovery is in progress, 619 // then no need to be bound. 620 if (!mHasPrinterDiscoverySession && !mHasActivePrintJobs) { 621 ensureUnbound(); 622 } 623 mServiceDied = false; 624 } 625 626 @Override 627 public void onServiceDisconnected(ComponentName name) { 628 mBinding = true; 629 } 630 } 631 632 private final class MyHandler extends Handler { 633 public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION = 1; 634 public static final int MSG_DESTROY_PRINTER_DISCOVERY_SESSION = 2; 635 public static final int MSG_START_PRINTER_DISCOVERY = 3; 636 public static final int MSG_STOP_PRINTER_DISCOVERY = 4; 637 public static final int MSG_VALIDATE_PRINTERS = 5; 638 public static final int MSG_START_PRINTER_STATE_TRACKING = 6; 639 public static final int MSG_STOP_PRINTER_STATE_TRACKING = 7; 640 public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 8; 641 public static final int MSG_ON_REQUEST_CANCEL_PRINT_JOB = 9; 642 public static final int MSG_ON_PRINT_JOB_QUEUED = 10; 643 public static final int MSG_DESTROY = 11; 644 public static final int MSG_BINDER_DIED = 12; 645 646 public MyHandler(Looper looper) { 647 super(looper, null, false); 648 } 649 650 @Override 651 @SuppressWarnings("unchecked") 652 public void handleMessage(Message message) { 653 switch (message.what) { 654 case MSG_CREATE_PRINTER_DISCOVERY_SESSION: { 655 handleCreatePrinterDiscoverySession(); 656 } break; 657 658 case MSG_DESTROY_PRINTER_DISCOVERY_SESSION: { 659 handleDestroyPrinterDiscoverySession(); 660 } break; 661 662 case MSG_START_PRINTER_DISCOVERY: { 663 List<PrinterId> priorityList = (ArrayList<PrinterId>) message.obj; 664 handleStartPrinterDiscovery(priorityList); 665 } break; 666 667 case MSG_STOP_PRINTER_DISCOVERY: { 668 handleStopPrinterDiscovery(); 669 } break; 670 671 case MSG_VALIDATE_PRINTERS: { 672 List<PrinterId> printerIds = (List<PrinterId>) message.obj; 673 handleValidatePrinters(printerIds); 674 } break; 675 676 case MSG_START_PRINTER_STATE_TRACKING: { 677 PrinterId printerId = (PrinterId) message.obj; 678 handleStartPrinterStateTracking(printerId); 679 } break; 680 681 case MSG_STOP_PRINTER_STATE_TRACKING: { 682 PrinterId printerId = (PrinterId) message.obj; 683 handleStopPrinterStateTracking(printerId); 684 } break; 685 686 case MSG_ON_ALL_PRINT_JOBS_HANDLED: { 687 handleOnAllPrintJobsHandled(); 688 } break; 689 690 case MSG_ON_REQUEST_CANCEL_PRINT_JOB: { 691 PrintJobInfo printJob = (PrintJobInfo) message.obj; 692 handleRequestCancelPrintJob(printJob); 693 } break; 694 695 case MSG_ON_PRINT_JOB_QUEUED: { 696 PrintJobInfo printJob = (PrintJobInfo) message.obj; 697 handleOnPrintJobQueued(printJob); 698 } break; 699 700 case MSG_DESTROY: { 701 handleDestroy(); 702 } break; 703 704 case MSG_BINDER_DIED: { 705 handleBinderDied(); 706 } break; 707 } 708 } 709 } 710 711 private static final class RemotePrintServiceClient extends IPrintServiceClient.Stub { 712 private final WeakReference<RemotePrintService> mWeakService; 713 714 public RemotePrintServiceClient(RemotePrintService service) { 715 mWeakService = new WeakReference<RemotePrintService>(service); 716 } 717 718 @Override 719 public List<PrintJobInfo> getPrintJobInfos() { 720 RemotePrintService service = mWeakService.get(); 721 if (service != null) { 722 final long identity = Binder.clearCallingIdentity(); 723 try { 724 return service.mSpooler.getPrintJobInfos(service.mComponentName, 725 PrintJobInfo.STATE_ANY_SCHEDULED, PrintManager.APP_ID_ANY); 726 } finally { 727 Binder.restoreCallingIdentity(identity); 728 } 729 } 730 return null; 731 } 732 733 @Override 734 public PrintJobInfo getPrintJobInfo(PrintJobId printJobId) { 735 RemotePrintService service = mWeakService.get(); 736 if (service != null) { 737 final long identity = Binder.clearCallingIdentity(); 738 try { 739 return service.mSpooler.getPrintJobInfo(printJobId, 740 PrintManager.APP_ID_ANY); 741 } finally { 742 Binder.restoreCallingIdentity(identity); 743 } 744 } 745 return null; 746 } 747 748 @Override 749 public boolean setPrintJobState(PrintJobId printJobId, int state, String error) { 750 RemotePrintService service = mWeakService.get(); 751 if (service != null) { 752 final long identity = Binder.clearCallingIdentity(); 753 try { 754 return service.mSpooler.setPrintJobState(printJobId, state, error); 755 } finally { 756 Binder.restoreCallingIdentity(identity); 757 } 758 } 759 return false; 760 } 761 762 @Override 763 public boolean setPrintJobTag(PrintJobId printJobId, String tag) { 764 RemotePrintService service = mWeakService.get(); 765 if (service != null) { 766 final long identity = Binder.clearCallingIdentity(); 767 try { 768 return service.mSpooler.setPrintJobTag(printJobId, tag); 769 } finally { 770 Binder.restoreCallingIdentity(identity); 771 } 772 } 773 return false; 774 } 775 776 @Override 777 public void writePrintJobData(ParcelFileDescriptor fd, PrintJobId printJobId) { 778 RemotePrintService service = mWeakService.get(); 779 if (service != null) { 780 final long identity = Binder.clearCallingIdentity(); 781 try { 782 service.mSpooler.writePrintJobData(fd, printJobId); 783 } finally { 784 Binder.restoreCallingIdentity(identity); 785 } 786 } 787 } 788 789 @Override 790 public void setProgress(@NonNull PrintJobId printJobId, 791 @FloatRange(from=0.0, to=1.0) float progress) { 792 RemotePrintService service = mWeakService.get(); 793 if (service != null) { 794 final long identity = Binder.clearCallingIdentity(); 795 try { 796 service.mSpooler.setProgress(printJobId, progress); 797 } finally { 798 Binder.restoreCallingIdentity(identity); 799 } 800 } 801 } 802 803 @Override 804 public void setStatus(@NonNull PrintJobId printJobId, @Nullable CharSequence status) { 805 RemotePrintService service = mWeakService.get(); 806 if (service != null) { 807 final long identity = Binder.clearCallingIdentity(); 808 try { 809 service.mSpooler.setStatus(printJobId, status); 810 } finally { 811 Binder.restoreCallingIdentity(identity); 812 } 813 } 814 } 815 816 @Override 817 public void setStatusRes(@NonNull PrintJobId printJobId, @StringRes int status, 818 @NonNull CharSequence appPackageName) { 819 RemotePrintService service = mWeakService.get(); 820 if (service != null) { 821 final long identity = Binder.clearCallingIdentity(); 822 try { 823 service.mSpooler.setStatus(printJobId, status, appPackageName); 824 } finally { 825 Binder.restoreCallingIdentity(identity); 826 } 827 } 828 } 829 830 @Override 831 @SuppressWarnings({"rawtypes", "unchecked"}) 832 public void onPrintersAdded(ParceledListSlice printers) { 833 RemotePrintService service = mWeakService.get(); 834 if (service != null) { 835 List<PrinterInfo> addedPrinters = (List<PrinterInfo>) printers.getList(); 836 throwIfPrinterIdsForPrinterInfoTampered(service.mComponentName, addedPrinters); 837 final long identity = Binder.clearCallingIdentity(); 838 try { 839 service.mCallbacks.onPrintersAdded(addedPrinters); 840 } finally { 841 Binder.restoreCallingIdentity(identity); 842 } 843 } 844 } 845 846 @Override 847 @SuppressWarnings({"rawtypes", "unchecked"}) 848 public void onPrintersRemoved(ParceledListSlice printerIds) { 849 RemotePrintService service = mWeakService.get(); 850 if (service != null) { 851 List<PrinterId> removedPrinterIds = (List<PrinterId>) printerIds.getList(); 852 throwIfPrinterIdsTampered(service.mComponentName, removedPrinterIds); 853 final long identity = Binder.clearCallingIdentity(); 854 try { 855 service.mCallbacks.onPrintersRemoved(removedPrinterIds); 856 } finally { 857 Binder.restoreCallingIdentity(identity); 858 } 859 } 860 } 861 862 private void throwIfPrinterIdsForPrinterInfoTampered(ComponentName serviceName, 863 List<PrinterInfo> printerInfos) { 864 final int printerInfoCount = printerInfos.size(); 865 for (int i = 0; i < printerInfoCount; i++) { 866 PrinterId printerId = printerInfos.get(i).getId(); 867 throwIfPrinterIdTampered(serviceName, printerId); 868 } 869 } 870 871 private void throwIfPrinterIdsTampered(ComponentName serviceName, 872 List<PrinterId> printerIds) { 873 final int printerIdCount = printerIds.size(); 874 for (int i = 0; i < printerIdCount; i++) { 875 PrinterId printerId = printerIds.get(i); 876 throwIfPrinterIdTampered(serviceName, printerId); 877 } 878 } 879 880 private void throwIfPrinterIdTampered(ComponentName serviceName, PrinterId printerId) { 881 if (printerId == null || !printerId.getServiceName().equals(serviceName)) { 882 throw new IllegalArgumentException("Invalid printer id: " + printerId); 883 } 884 } 885 886 @Override 887 public void onCustomPrinterIconLoaded(PrinterId printerId, Icon icon) 888 throws RemoteException { 889 RemotePrintService service = mWeakService.get(); 890 if (service != null) { 891 final long identity = Binder.clearCallingIdentity(); 892 try { 893 service.mCallbacks.onCustomPrinterIconLoaded(printerId, icon); 894 } finally { 895 Binder.restoreCallingIdentity(identity); 896 } 897 } 898 } 899 } 900 } 901