PrintManager.java revision d8dbc13b47bec3248a86a505a30af9d0474240dc
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 17package android.print; 18 19import android.content.Context; 20import android.content.IntentSender; 21import android.content.IntentSender.SendIntentException; 22import android.os.Bundle; 23import android.os.CancellationSignal; 24import android.os.Handler; 25import android.os.Looper; 26import android.os.Message; 27import android.os.ParcelFileDescriptor; 28import android.os.RemoteException; 29import android.print.PrintDocumentAdapter.LayoutResultCallback; 30import android.print.PrintDocumentAdapter.WriteResultCallback; 31import android.printservice.PrintServiceInfo; 32import android.text.TextUtils; 33import android.util.ArrayMap; 34import android.util.Log; 35 36import com.android.internal.os.SomeArgs; 37 38import libcore.io.IoUtils; 39 40import java.lang.ref.WeakReference; 41import java.util.ArrayList; 42import java.util.Collections; 43import java.util.List; 44import java.util.Map; 45 46/** 47 * System level service for accessing the printing capabilities of the platform. 48 * <p> 49 * To obtain a handle to the print manager do the following: 50 * </p> 51 * <pre> 52 * PrintManager printManager = 53 * (PrintManager) context.getSystemService(Context.PRINT_SERVICE); 54 * </pre> 55 */ 56public final class PrintManager { 57 58 private static final String LOG_TAG = "PrintManager"; 59 60 /** @hide */ 61 public static final int APP_ID_ANY = -2; 62 63 private final Context mContext; 64 65 private final IPrintManager mService; 66 67 private final int mUserId; 68 69 private final int mAppId; 70 71 private final PrintClient mPrintClient; 72 73 private final Handler mHandler; 74 75 private Map<PrintJobStateChangeListener, PrintJobStateChangeListenerWrapper> mPrintJobStateChangeListeners; 76 77 /** @hide */ 78 public interface PrintJobStateChangeListener { 79 80 /** 81 * Callback notifying that a print job state changed. 82 * 83 * @param printJobId The print job id. 84 */ 85 public void onPrintJobsStateChanged(PrintJobId printJobId); 86 } 87 88 /** 89 * Creates a new instance. 90 * 91 * @param context The current context in which to operate. 92 * @param service The backing system service. 93 * 94 * @hide 95 */ 96 public PrintManager(Context context, IPrintManager service, int userId, int appId) { 97 mContext = context; 98 mService = service; 99 mUserId = userId; 100 mAppId = appId; 101 mPrintClient = new PrintClient(this); 102 mHandler = new Handler(context.getMainLooper(), null, false) { 103 @Override 104 public void handleMessage(Message message) { 105 SomeArgs args = (SomeArgs) message.obj; 106 Context context = (Context) args.arg1; 107 IntentSender intent = (IntentSender) args.arg2; 108 args.recycle(); 109 try { 110 context.startIntentSender(intent, null, 0, 0, 0); 111 } catch (SendIntentException sie) { 112 Log.e(LOG_TAG, "Couldn't start print job config activity.", sie); 113 } 114 } 115 }; 116 } 117 118 /** 119 * Creates an instance that can access all print jobs. 120 * 121 * @param userId The user id for which to get all print jobs. 122 * @return An instance if the caller has the permission to access 123 * all print jobs, null otherwise. 124 * @hide 125 */ 126 public PrintManager getGlobalPrintManagerForUser(int userId) { 127 return new PrintManager(mContext, mService, userId, APP_ID_ANY); 128 } 129 130 PrintJobInfo getPrintJobInfo(PrintJobId printJobId) { 131 try { 132 return mService.getPrintJobInfo(printJobId, mAppId, mUserId); 133 } catch (RemoteException re) { 134 Log.e(LOG_TAG, "Error getting a print job info:" + printJobId, re); 135 } 136 return null; 137 } 138 139 /** 140 * Adds a listener for observing the state of print jobs. 141 * 142 * @param listener The listener to add. 143 * 144 * @hide 145 */ 146 public void addPrintJobStateChangeListener(PrintJobStateChangeListener listener) { 147 if (mPrintJobStateChangeListeners == null) { 148 mPrintJobStateChangeListeners = new ArrayMap<PrintJobStateChangeListener, 149 PrintJobStateChangeListenerWrapper>(); 150 } 151 PrintJobStateChangeListenerWrapper wrappedListener = 152 new PrintJobStateChangeListenerWrapper(listener); 153 try { 154 mService.addPrintJobStateChangeListener(wrappedListener, mAppId, mUserId); 155 mPrintJobStateChangeListeners.put(listener, wrappedListener); 156 } catch (RemoteException re) { 157 Log.e(LOG_TAG, "Error adding print job state change listener", re); 158 } 159 } 160 161 /** 162 * Removes a listener for observing the state of print jobs. 163 * 164 * @param listener The listener to remove. 165 * 166 * @hide 167 */ 168 public void removePrintJobStateChangeListener(PrintJobStateChangeListener listener) { 169 if (mPrintJobStateChangeListeners == null) { 170 return; 171 } 172 PrintJobStateChangeListenerWrapper wrappedListener = 173 mPrintJobStateChangeListeners.remove(listener); 174 if (wrappedListener == null) { 175 return; 176 } 177 if (mPrintJobStateChangeListeners.isEmpty()) { 178 mPrintJobStateChangeListeners = null; 179 } 180 try { 181 mService.removePrintJobStateChangeListener(wrappedListener, mUserId); 182 } catch (RemoteException re) { 183 Log.e(LOG_TAG, "Error removing print job state change listener", re); 184 } 185 } 186 187 /** 188 * Gets a print job given its id. 189 * 190 * @return The print job list. 191 * 192 * @see PrintJob 193 * 194 * @hide 195 */ 196 public PrintJob getPrintJob(PrintJobId printJobId) { 197 try { 198 PrintJobInfo printJob = mService.getPrintJobInfo(printJobId, mAppId, mUserId); 199 if (printJob != null) { 200 return new PrintJob(printJob, this); 201 } 202 } catch (RemoteException re) { 203 Log.e(LOG_TAG, "Error getting print job", re); 204 } 205 return null; 206 } 207 208 /** 209 * Gets the print jobs for this application. 210 * 211 * @return The print job list. 212 * 213 * @see PrintJob 214 */ 215 public List<PrintJob> getPrintJobs() { 216 try { 217 List<PrintJobInfo> printJobInfos = mService.getPrintJobInfos(mAppId, mUserId); 218 if (printJobInfos == null) { 219 return Collections.emptyList(); 220 } 221 final int printJobCount = printJobInfos.size(); 222 List<PrintJob> printJobs = new ArrayList<PrintJob>(printJobCount); 223 for (int i = 0; i < printJobCount; i++) { 224 printJobs.add(new PrintJob(printJobInfos.get(i), this)); 225 } 226 return printJobs; 227 } catch (RemoteException re) { 228 Log.e(LOG_TAG, "Error getting print jobs", re); 229 } 230 return Collections.emptyList(); 231 } 232 233 void cancelPrintJob(PrintJobId printJobId) { 234 try { 235 mService.cancelPrintJob(printJobId, mAppId, mUserId); 236 } catch (RemoteException re) { 237 Log.e(LOG_TAG, "Error cancleing a print job: " + printJobId, re); 238 } 239 } 240 241 void restartPrintJob(PrintJobId printJobId) { 242 try { 243 mService.restartPrintJob(printJobId, mAppId, mUserId); 244 } catch (RemoteException re) { 245 Log.e(LOG_TAG, "Error restarting a print job: " + printJobId, re); 246 } 247 } 248 249 /** 250 * Creates a print job for printing a {@link PrintDocumentAdapter} with default print 251 * attributes. 252 * 253 * @param printJobName A name for the new print job. 254 * @param documentAdapter An adapter that emits the document to print. 255 * @param attributes The default print job attributes. 256 * @return The created print job on success or null on failure. 257 * @see PrintJob 258 */ 259 public PrintJob print(String printJobName, PrintDocumentAdapter documentAdapter, 260 PrintAttributes attributes) { 261 if (TextUtils.isEmpty(printJobName)) { 262 throw new IllegalArgumentException("priintJobName cannot be empty"); 263 } 264 PrintDocumentAdapterDelegate delegate = new PrintDocumentAdapterDelegate(documentAdapter, 265 mContext.getMainLooper()); 266 try { 267 PrintJobInfo printJob = mService.print(printJobName, mPrintClient, delegate, 268 attributes, mAppId, mUserId); 269 if (printJob != null) { 270 return new PrintJob(printJob, this); 271 } 272 } catch (RemoteException re) { 273 Log.e(LOG_TAG, "Error creating a print job", re); 274 } 275 return null; 276 } 277 278 /** 279 * Gets the list of enabled print services. 280 * 281 * @return The enabled service list or an empty list. 282 * 283 * @hide 284 */ 285 public List<PrintServiceInfo> getEnabledPrintServices() { 286 try { 287 List<PrintServiceInfo> enabledServices = mService.getEnabledPrintServices(mUserId); 288 if (enabledServices != null) { 289 return enabledServices; 290 } 291 } catch (RemoteException re) { 292 Log.e(LOG_TAG, "Error getting the enabled print services", re); 293 } 294 return Collections.emptyList(); 295 } 296 297 /** 298 * Gets the list of installed print services. 299 * 300 * @return The installed service list or an empty list. 301 * 302 * @hide 303 */ 304 public List<PrintServiceInfo> getInstalledPrintServices() { 305 try { 306 List<PrintServiceInfo> installedServices = mService.getInstalledPrintServices(mUserId); 307 if (installedServices != null) { 308 return installedServices; 309 } 310 } catch (RemoteException re) { 311 Log.e(LOG_TAG, "Error getting the installed print services", re); 312 } 313 return Collections.emptyList(); 314 } 315 316 /** 317 * @hide 318 */ 319 public PrinterDiscoverySession createPrinterDiscoverySession() { 320 return new PrinterDiscoverySession(mService, mContext, mUserId); 321 } 322 323 private static final class PrintClient extends IPrintClient.Stub { 324 325 private final WeakReference<PrintManager> mWeakPrintManager; 326 327 public PrintClient(PrintManager manager) { 328 mWeakPrintManager = new WeakReference<PrintManager>(manager); 329 } 330 331 @Override 332 public void startPrintJobConfigActivity(IntentSender intent) { 333 PrintManager manager = mWeakPrintManager.get(); 334 if (manager != null) { 335 SomeArgs args = SomeArgs.obtain(); 336 args.arg1 = manager.mContext; 337 args.arg2 = intent; 338 manager.mHandler.obtainMessage(0, args).sendToTarget(); 339 } 340 } 341 } 342 343 private static final class PrintDocumentAdapterDelegate extends IPrintDocumentAdapter.Stub { 344 345 private final Object mLock = new Object(); 346 347 private CancellationSignal mLayoutOrWriteCancellation; 348 349 private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK - cleared in finish() 350 351 private Handler mHandler; // Strong reference OK - cleared in finish() 352 353 public PrintDocumentAdapterDelegate(PrintDocumentAdapter documentAdapter, Looper looper) { 354 mDocumentAdapter = documentAdapter; 355 mHandler = new MyHandler(looper); 356 } 357 358 @Override 359 public void start() { 360 mHandler.sendEmptyMessage(MyHandler.MSG_START); 361 } 362 363 @Override 364 public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes, 365 ILayoutResultCallback callback, Bundle metadata, int sequence) { 366 synchronized (mLock) { 367 if (mLayoutOrWriteCancellation != null) { 368 mLayoutOrWriteCancellation.cancel(); 369 } 370 } 371 SomeArgs args = SomeArgs.obtain(); 372 args.arg1 = oldAttributes; 373 args.arg2 = newAttributes; 374 args.arg3 = callback; 375 args.arg4 = metadata; 376 args.argi1 = sequence; 377 mHandler.removeMessages(MyHandler.MSG_LAYOUT); 378 mHandler.obtainMessage(MyHandler.MSG_LAYOUT, args).sendToTarget(); 379 } 380 381 @Override 382 public void write(PageRange[] pages, ParcelFileDescriptor fd, 383 IWriteResultCallback callback, int sequence) { 384 synchronized (mLock) { 385 if (mLayoutOrWriteCancellation != null) { 386 mLayoutOrWriteCancellation.cancel(); 387 } 388 } 389 SomeArgs args = SomeArgs.obtain(); 390 args.arg1 = pages; 391 args.arg2 = fd; 392 args.arg3 = callback; 393 args.argi1 = sequence; 394 mHandler.removeMessages(MyHandler.MSG_WRITE); 395 mHandler.obtainMessage(MyHandler.MSG_WRITE, args).sendToTarget(); 396 } 397 398 @Override 399 public void finish() { 400 mHandler.sendEmptyMessage(MyHandler.MSG_FINISH); 401 } 402 403 private boolean isFinished() { 404 return mDocumentAdapter == null; 405 } 406 407 private void doFinish() { 408 mDocumentAdapter = null; 409 mHandler = null; 410 mLayoutOrWriteCancellation = null; 411 } 412 413 private final class MyHandler extends Handler { 414 public static final int MSG_START = 1; 415 public static final int MSG_LAYOUT = 2; 416 public static final int MSG_WRITE = 3; 417 public static final int MSG_FINISH = 4; 418 419 public MyHandler(Looper looper) { 420 super(looper, null, true); 421 } 422 423 @Override 424 public void handleMessage(Message message) { 425 if (isFinished()) { 426 return; 427 } 428 switch (message.what) { 429 case MSG_START: { 430 mDocumentAdapter.onStart(); 431 } break; 432 433 case MSG_LAYOUT: { 434 SomeArgs args = (SomeArgs) message.obj; 435 PrintAttributes oldAttributes = (PrintAttributes) args.arg1; 436 PrintAttributes newAttributes = (PrintAttributes) args.arg2; 437 ILayoutResultCallback callback = (ILayoutResultCallback) args.arg3; 438 Bundle metadata = (Bundle) args.arg4; 439 final int sequence = args.argi1; 440 args.recycle(); 441 442 CancellationSignal cancellation = new CancellationSignal(); 443 synchronized (mLock) { 444 mLayoutOrWriteCancellation = cancellation; 445 } 446 447 mDocumentAdapter.onLayout(oldAttributes, newAttributes, cancellation, 448 new MyLayoutResultCallback(callback, sequence), metadata); 449 } break; 450 451 case MSG_WRITE: { 452 SomeArgs args = (SomeArgs) message.obj; 453 PageRange[] pages = (PageRange[]) args.arg1; 454 ParcelFileDescriptor fd = (ParcelFileDescriptor) args.arg2; 455 IWriteResultCallback callback = (IWriteResultCallback) args.arg3; 456 final int sequence = args.argi1; 457 args.recycle(); 458 459 CancellationSignal cancellation = new CancellationSignal(); 460 synchronized (mLock) { 461 mLayoutOrWriteCancellation = cancellation; 462 } 463 464 mDocumentAdapter.onWrite(pages, fd, cancellation, 465 new MyWriteResultCallback(callback, fd, sequence)); 466 } break; 467 468 case MSG_FINISH: { 469 mDocumentAdapter.onFinish(); 470 doFinish(); 471 } break; 472 473 default: { 474 throw new IllegalArgumentException("Unknown message: " 475 + message.what); 476 } 477 } 478 } 479 } 480 481 private final class MyLayoutResultCallback extends LayoutResultCallback { 482 private ILayoutResultCallback mCallback; 483 private final int mSequence; 484 485 public MyLayoutResultCallback(ILayoutResultCallback callback, 486 int sequence) { 487 mCallback = callback; 488 mSequence = sequence; 489 } 490 491 @Override 492 public void onLayoutFinished(PrintDocumentInfo info, boolean changed) { 493 if (info == null) { 494 throw new NullPointerException("document info cannot be null"); 495 } 496 final ILayoutResultCallback callback; 497 synchronized (mLock) { 498 callback = mCallback; 499 clearLocked(); 500 } 501 if (callback != null) { 502 try { 503 callback.onLayoutFinished(info, changed, mSequence); 504 } catch (RemoteException re) { 505 Log.e(LOG_TAG, "Error calling onLayoutFinished", re); 506 } 507 } 508 } 509 510 @Override 511 public void onLayoutFailed(CharSequence error) { 512 final ILayoutResultCallback callback; 513 synchronized (mLock) { 514 callback = mCallback; 515 clearLocked(); 516 } 517 if (callback != null) { 518 try { 519 callback.onLayoutFailed(error, mSequence); 520 } catch (RemoteException re) { 521 Log.e(LOG_TAG, "Error calling onLayoutFailed", re); 522 } 523 } 524 } 525 526 @Override 527 public void onLayoutCancelled() { 528 synchronized (mLock) { 529 clearLocked(); 530 } 531 } 532 533 private void clearLocked() { 534 mLayoutOrWriteCancellation = null; 535 mCallback = null; 536 } 537 } 538 539 private final class MyWriteResultCallback extends WriteResultCallback { 540 private ParcelFileDescriptor mFd; 541 private int mSequence; 542 private IWriteResultCallback mCallback; 543 544 public MyWriteResultCallback(IWriteResultCallback callback, 545 ParcelFileDescriptor fd, int sequence) { 546 mFd = fd; 547 mSequence = sequence; 548 mCallback = callback; 549 } 550 551 @Override 552 public void onWriteFinished(PageRange[] pages) { 553 final IWriteResultCallback callback; 554 synchronized (mLock) { 555 callback = mCallback; 556 clearLocked(); 557 } 558 if (pages == null) { 559 throw new IllegalArgumentException("pages cannot be null"); 560 } 561 if (pages.length == 0) { 562 throw new IllegalArgumentException("pages cannot be empty"); 563 } 564 if (callback != null) { 565 try { 566 callback.onWriteFinished(pages, mSequence); 567 } catch (RemoteException re) { 568 Log.e(LOG_TAG, "Error calling onWriteFinished", re); 569 } 570 } 571 } 572 573 @Override 574 public void onWriteFailed(CharSequence error) { 575 final IWriteResultCallback callback; 576 synchronized (mLock) { 577 callback = mCallback; 578 clearLocked(); 579 } 580 if (callback != null) { 581 try { 582 callback.onWriteFailed(error, mSequence); 583 } catch (RemoteException re) { 584 Log.e(LOG_TAG, "Error calling onWriteFailed", re); 585 } 586 } 587 } 588 589 @Override 590 public void onWriteCancelled() { 591 synchronized (mLock) { 592 clearLocked(); 593 } 594 } 595 596 private void clearLocked() { 597 mLayoutOrWriteCancellation = null; 598 IoUtils.closeQuietly(mFd); 599 mCallback = null; 600 mFd = null; 601 } 602 } 603 } 604 605 private static final class PrintJobStateChangeListenerWrapper extends 606 IPrintJobStateChangeListener.Stub { 607 private final WeakReference<PrintJobStateChangeListener> mWeakListener; 608 609 public PrintJobStateChangeListenerWrapper(PrintJobStateChangeListener listener) { 610 mWeakListener = new WeakReference<PrintJobStateChangeListener>(listener); 611 } 612 613 @Override 614 public void onPrintJobStateChanged(PrintJobId printJobId) { 615 PrintJobStateChangeListener listener = mWeakListener.get(); 616 if (listener != null) { 617 listener.onPrintJobsStateChanged(printJobId); 618 } 619 } 620 } 621} 622