PrintManager.java revision 269403b032f965ff3847eb982c2f697229dc5a92
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.text.TextUtils; 32import android.util.Log; 33 34import com.android.internal.os.SomeArgs; 35 36import libcore.io.IoUtils; 37 38import java.io.File; 39import java.io.FileDescriptor; 40import java.lang.ref.WeakReference; 41import java.util.ArrayList; 42import java.util.Collections; 43import java.util.List; 44 45/** 46 * System level service for accessing the printing capabilities of the platform. 47 * <p> 48 * To obtain a handle to the print manager do the following: 49 * </p> 50 * <pre> 51 * PrintManager printManager = 52 * (PrintManager) context.getSystemService(Context.PRINT_SERVICE); 53 * </pre> 54 */ 55public final class PrintManager { 56 57 private static final String LOG_TAG = "PrintManager"; 58 59 /** @hide */ 60 public static final int APP_ID_ANY = -2; 61 62 private final Context mContext; 63 64 private final IPrintManager mService; 65 66 private final int mUserId; 67 68 private final int mAppId; 69 70 private final PrintClient mPrintClient; 71 72 private final Handler mHandler; 73 74 /** 75 * Creates a new instance. 76 * 77 * @param context The current context in which to operate. 78 * @param service The backing system service. 79 * 80 * @hide 81 */ 82 public PrintManager(Context context, IPrintManager service, int userId, int appId) { 83 mContext = context; 84 mService = service; 85 mUserId = userId; 86 mAppId = appId; 87 mPrintClient = new PrintClient(this); 88 mHandler = new Handler(context.getMainLooper(), null, false) { 89 @Override 90 public void handleMessage(Message message) { 91 SomeArgs args = (SomeArgs) message.obj; 92 Context context = (Context) args.arg1; 93 IntentSender intent = (IntentSender) args.arg2; 94 args.recycle(); 95 try { 96 context.startIntentSender(intent, null, 0, 0, 0); 97 } catch (SendIntentException sie) { 98 Log.e(LOG_TAG, "Couldn't start print job config activity.", sie); 99 } 100 } 101 }; 102 } 103 104 /** 105 * Creates an instance that can access all print jobs. 106 * 107 * @param userId The user id for which to get all print jobs. 108 * @return An instance if the caller has the permission to access 109 * all print jobs, null otherwise. 110 * 111 * @hide 112 */ 113 public PrintManager getGlobalPrintManagerForUser(int userId) { 114 return new PrintManager(mContext, mService, userId, APP_ID_ANY); 115 } 116 117 PrintJobInfo getPrintJobInfo(int printJobId) { 118 try { 119 return mService.getPrintJobInfo(printJobId, mAppId, mUserId); 120 } catch (RemoteException re) { 121 Log.e(LOG_TAG, "Error getting a print job info:" + printJobId, re); 122 } 123 return null; 124 } 125 126 /** 127 * Gets the print jobs for this application. 128 * 129 * @return The print job list. 130 * 131 * @see PrintJob 132 */ 133 public List<PrintJob> getPrintJobs() { 134 try { 135 List<PrintJobInfo> printJobInfos = mService.getPrintJobInfos(mAppId, mUserId); 136 if (printJobInfos == null) { 137 return Collections.emptyList(); 138 } 139 final int printJobCount = printJobInfos.size(); 140 List<PrintJob> printJobs = new ArrayList<PrintJob>(printJobCount); 141 for (int i = 0; i < printJobCount; i++) { 142 printJobs.add(new PrintJob(printJobInfos.get(i), this)); 143 } 144 return printJobs; 145 } catch (RemoteException re) { 146 Log.e(LOG_TAG, "Error getting print jobs", re); 147 } 148 return Collections.emptyList(); 149 } 150 151 void cancelPrintJob(int printJobId) { 152 try { 153 mService.cancelPrintJob(printJobId, mAppId, mUserId); 154 } catch (RemoteException re) { 155 Log.e(LOG_TAG, "Error cancleing a print job: " + printJobId, re); 156 } 157 } 158 159 /** 160 * Creates a print job for printing a file with default print attributes. 161 * 162 * @param printJobName A name for the new print job. 163 * @param pdfFile The PDF file to print. 164 * @param documentInfo Information about the printed document. 165 * @param attributes The default print job attributes. 166 * @return The created print job. 167 * 168 * @see PrintJob 169 */ 170 public PrintJob print(String printJobName, File pdfFile, PrintDocumentInfo documentInfo, 171 PrintAttributes attributes) { 172 PrintFileDocumentAdapter documentAdapter = new PrintFileDocumentAdapter( 173 mContext, pdfFile, documentInfo); 174 return print(printJobName, documentAdapter, attributes); 175 } 176 177 /** 178 * Creates a print job for printing a {@link PrintDocumentAdapter} with default print 179 * attributes. 180 * 181 * @param printJobName A name for the new print job. 182 * @param documentAdapter An adapter that emits the document to print. 183 * @param attributes The default print job attributes. 184 * @return The created print job. 185 * 186 * @see PrintJob 187 */ 188 public PrintJob print(String printJobName, PrintDocumentAdapter documentAdapter, 189 PrintAttributes attributes) { 190 if (TextUtils.isEmpty(printJobName)) { 191 throw new IllegalArgumentException("priintJobName cannot be empty"); 192 } 193 PrintDocumentAdapterDelegate delegate = new PrintDocumentAdapterDelegate(documentAdapter, 194 mContext.getMainLooper()); 195 try { 196 PrintJobInfo printJob = mService.print(printJobName, mPrintClient, delegate, 197 attributes, mAppId, mUserId); 198 if (printJob != null) { 199 return new PrintJob(printJob, this); 200 } 201 } catch (RemoteException re) { 202 Log.e(LOG_TAG, "Error creating a print job", re); 203 } 204 return null; 205 } 206 207 private static final class PrintClient extends IPrintClient.Stub { 208 209 private final WeakReference<PrintManager> mWeakPrintManager; 210 211 public PrintClient(PrintManager manager) { 212 mWeakPrintManager = new WeakReference<PrintManager>(manager); 213 } 214 215 @Override 216 public void startPrintJobConfigActivity(IntentSender intent) { 217 PrintManager manager = mWeakPrintManager.get(); 218 if (manager != null) { 219 SomeArgs args = SomeArgs.obtain(); 220 args.arg1 = manager.mContext; 221 args.arg2 = intent; 222 manager.mHandler.obtainMessage(0, args).sendToTarget(); 223 } 224 } 225 } 226 227 private static final class PrintDocumentAdapterDelegate extends IPrintDocumentAdapter.Stub { 228 229 private final Object mLock = new Object(); 230 231 private CancellationSignal mLayoutOrWriteCancellation; 232 233 private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK - cleared in finish() 234 235 private Handler mHandler; // Strong reference OK - cleared in finish() 236 237 public PrintDocumentAdapterDelegate(PrintDocumentAdapter documentAdapter, Looper looper) { 238 mDocumentAdapter = documentAdapter; 239 mHandler = new MyHandler(looper); 240 } 241 242 @Override 243 public void start() { 244 mHandler.sendEmptyMessage(MyHandler.MSG_START); 245 } 246 247 @Override 248 public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes, 249 ILayoutResultCallback callback, Bundle metadata, int sequence) { 250 synchronized (mLock) { 251 if (mLayoutOrWriteCancellation != null) { 252 mLayoutOrWriteCancellation.cancel(); 253 } 254 } 255 SomeArgs args = SomeArgs.obtain(); 256 args.arg1 = oldAttributes; 257 args.arg2 = newAttributes; 258 args.arg3 = callback; 259 args.arg4 = metadata; 260 args.argi1 = sequence; 261 mHandler.removeMessages(MyHandler.MSG_LAYOUT); 262 mHandler.obtainMessage(MyHandler.MSG_LAYOUT, args).sendToTarget(); 263 } 264 265 @Override 266 public void write(PageRange[] pages, ParcelFileDescriptor fd, 267 IWriteResultCallback callback, int sequence) { 268 synchronized (mLock) { 269 if (mLayoutOrWriteCancellation != null) { 270 mLayoutOrWriteCancellation.cancel(); 271 } 272 } 273 SomeArgs args = SomeArgs.obtain(); 274 args.arg1 = pages; 275 args.arg2 = fd.getFileDescriptor(); 276 args.arg3 = callback; 277 args.argi1 = sequence; 278 mHandler.removeMessages(MyHandler.MSG_WRITE); 279 mHandler.obtainMessage(MyHandler.MSG_WRITE, args).sendToTarget(); 280 } 281 282 @Override 283 public void finish() { 284 mHandler.sendEmptyMessage(MyHandler.MSG_FINISH); 285 } 286 287 private boolean isFinished() { 288 return mDocumentAdapter == null; 289 } 290 291 private void doFinish() { 292 mDocumentAdapter = null; 293 mHandler = null; 294 mLayoutOrWriteCancellation = null; 295 } 296 297 private final class MyHandler extends Handler { 298 public static final int MSG_START = 1; 299 public static final int MSG_LAYOUT = 2; 300 public static final int MSG_WRITE = 3; 301 public static final int MSG_FINISH = 4; 302 303 public MyHandler(Looper looper) { 304 super(looper, null, true); 305 } 306 307 @Override 308 public void handleMessage(Message message) { 309 if (isFinished()) { 310 return; 311 } 312 switch (message.what) { 313 case MSG_START: { 314 mDocumentAdapter.onStart(); 315 } break; 316 317 case MSG_LAYOUT: { 318 SomeArgs args = (SomeArgs) message.obj; 319 PrintAttributes oldAttributes = (PrintAttributes) args.arg1; 320 PrintAttributes newAttributes = (PrintAttributes) args.arg2; 321 ILayoutResultCallback callback = (ILayoutResultCallback) args.arg3; 322 Bundle metadata = (Bundle) args.arg4; 323 final int sequence = args.argi1; 324 args.recycle(); 325 326 CancellationSignal cancellation = new CancellationSignal(); 327 synchronized (mLock) { 328 mLayoutOrWriteCancellation = cancellation; 329 } 330 331 mDocumentAdapter.onLayout(oldAttributes, newAttributes, cancellation, 332 new MyLayoutResultCallback(callback, sequence), metadata); 333 } break; 334 335 case MSG_WRITE: { 336 SomeArgs args = (SomeArgs) message.obj; 337 PageRange[] pages = (PageRange[]) args.arg1; 338 FileDescriptor fd = (FileDescriptor) args.arg2; 339 IWriteResultCallback callback = (IWriteResultCallback) args.arg3; 340 final int sequence = args.argi1; 341 args.recycle(); 342 343 CancellationSignal cancellation = new CancellationSignal(); 344 synchronized (mLock) { 345 mLayoutOrWriteCancellation = cancellation; 346 } 347 348 mDocumentAdapter.onWrite(pages, fd, cancellation, 349 new MyWriteResultCallback(callback, fd, sequence)); 350 } break; 351 352 case MSG_FINISH: { 353 mDocumentAdapter.onFinish(); 354 doFinish(); 355 } break; 356 357 default: { 358 throw new IllegalArgumentException("Unknown message: " 359 + message.what); 360 } 361 } 362 } 363 } 364 365 private final class MyLayoutResultCallback extends LayoutResultCallback { 366 private ILayoutResultCallback mCallback; 367 private final int mSequence; 368 369 public MyLayoutResultCallback(ILayoutResultCallback callback, 370 int sequence) { 371 mCallback = callback; 372 mSequence = sequence; 373 } 374 375 @Override 376 public void onLayoutFinished(PrintDocumentInfo info, boolean changed) { 377 if (info == null) { 378 throw new NullPointerException("document info cannot be null"); 379 } 380 final ILayoutResultCallback callback; 381 synchronized (mLock) { 382 callback = mCallback; 383 clearLocked(); 384 } 385 if (callback != null) { 386 try { 387 callback.onLayoutFinished(info, changed, mSequence); 388 } catch (RemoteException re) { 389 Log.e(LOG_TAG, "Error calling onLayoutFinished", re); 390 } 391 } 392 } 393 394 @Override 395 public void onLayoutFailed(CharSequence error) { 396 final ILayoutResultCallback callback; 397 synchronized (mLock) { 398 callback = mCallback; 399 clearLocked(); 400 } 401 if (callback != null) { 402 try { 403 callback.onLayoutFailed(error, mSequence); 404 } catch (RemoteException re) { 405 Log.e(LOG_TAG, "Error calling onLayoutFailed", re); 406 } 407 } 408 } 409 410 @Override 411 public void onLayoutCancelled() { 412 synchronized (mLock) { 413 clearLocked(); 414 } 415 } 416 417 private void clearLocked() { 418 mLayoutOrWriteCancellation = null; 419 mCallback = null; 420 } 421 } 422 423 private final class MyWriteResultCallback extends WriteResultCallback { 424 private FileDescriptor mFd; 425 private int mSequence; 426 private IWriteResultCallback mCallback; 427 428 public MyWriteResultCallback(IWriteResultCallback callback, 429 FileDescriptor fd, int sequence) { 430 mFd = fd; 431 mSequence = sequence; 432 mCallback = callback; 433 } 434 435 @Override 436 public void onWriteFinished(PageRange[] pages) { 437 final IWriteResultCallback callback; 438 synchronized (mLock) { 439 callback = mCallback; 440 clearLocked(); 441 } 442 if (pages == null) { 443 throw new IllegalArgumentException("pages cannot be null"); 444 } 445 if (pages.length == 0) { 446 throw new IllegalArgumentException("pages cannot be empty"); 447 } 448 if (callback != null) { 449 try { 450 callback.onWriteFinished(pages, mSequence); 451 } catch (RemoteException re) { 452 Log.e(LOG_TAG, "Error calling onWriteFinished", re); 453 } 454 } 455 } 456 457 @Override 458 public void onWriteFailed(CharSequence error) { 459 final IWriteResultCallback callback; 460 synchronized (mLock) { 461 callback = mCallback; 462 clearLocked(); 463 } 464 if (callback != null) { 465 try { 466 callback.onWriteFailed(error, mSequence); 467 } catch (RemoteException re) { 468 Log.e(LOG_TAG, "Error calling onWriteFailed", re); 469 } 470 } 471 } 472 473 @Override 474 public void onWriteCancelled() { 475 synchronized (mLock) { 476 clearLocked(); 477 } 478 } 479 480 private void clearLocked() { 481 mLayoutOrWriteCancellation = null; 482 IoUtils.closeQuietly(mFd); 483 mCallback = null; 484 mFd = null; 485 } 486 } 487 } 488} 489