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