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 android.support.v4.print; 18 19 import android.content.Context; 20 import android.graphics.Bitmap; 21 import android.graphics.BitmapFactory; 22 import android.graphics.Canvas; 23 import android.graphics.ColorMatrix; 24 import android.graphics.ColorMatrixColorFilter; 25 import android.graphics.Matrix; 26 import android.graphics.Paint; 27 import android.graphics.RectF; 28 import android.graphics.pdf.PdfDocument; 29 import android.net.Uri; 30 import android.os.AsyncTask; 31 import android.os.Build; 32 import android.os.Bundle; 33 import android.os.CancellationSignal; 34 import android.os.ParcelFileDescriptor; 35 import android.print.PageRange; 36 import android.print.PrintAttributes; 37 import android.print.PrintDocumentAdapter; 38 import android.print.PrintDocumentInfo; 39 import android.print.PrintManager; 40 import android.print.pdf.PrintedPdfDocument; 41 import android.support.annotation.IntDef; 42 import android.support.annotation.RequiresApi; 43 import android.util.Log; 44 45 import java.io.FileNotFoundException; 46 import java.io.FileOutputStream; 47 import java.io.IOException; 48 import java.io.InputStream; 49 import java.lang.annotation.Retention; 50 import java.lang.annotation.RetentionPolicy; 51 52 /** 53 * Helper for printing bitmaps. 54 */ 55 public final class PrintHelper { 56 /** 57 * image will be scaled but leave white space 58 */ 59 public static final int SCALE_MODE_FIT = 1; 60 61 /** 62 * image will fill the paper and be cropped (default) 63 */ 64 public static final int SCALE_MODE_FILL = 2; 65 66 /** 67 * this is a black and white image 68 */ 69 public static final int COLOR_MODE_MONOCHROME = PrintAttributes.COLOR_MODE_MONOCHROME; 70 71 /** 72 * this is a color image (default) 73 */ 74 public static final int COLOR_MODE_COLOR = PrintAttributes.COLOR_MODE_COLOR; 75 76 /** 77 * Print the image in landscape orientation (default). 78 */ 79 public static final int ORIENTATION_LANDSCAPE = 1; 80 81 /** 82 * Print the image in portrait orientation. 83 */ 84 public static final int ORIENTATION_PORTRAIT = 2; 85 86 /** 87 * Callback for observing when a print operation is completed. 88 * When print is finished either the system acquired the 89 * document to print or printing was cancelled. 90 */ 91 public interface OnPrintFinishCallback { 92 /** 93 * Called when a print operation is finished. 94 */ onFinish()95 void onFinish(); 96 } 97 98 @IntDef({SCALE_MODE_FIT, SCALE_MODE_FILL}) 99 @Retention(RetentionPolicy.SOURCE) 100 private @interface ScaleMode {} 101 102 @IntDef({COLOR_MODE_MONOCHROME, COLOR_MODE_COLOR}) 103 @Retention(RetentionPolicy.SOURCE) 104 private @interface ColorMode {} 105 106 @IntDef({ORIENTATION_LANDSCAPE, ORIENTATION_PORTRAIT}) 107 @Retention(RetentionPolicy.SOURCE) 108 private @interface Orientation {} 109 110 private final PrintHelperVersionImpl mImpl; 111 112 /** 113 * Gets whether the system supports printing. 114 * 115 * @return True if printing is supported. 116 */ systemSupportsPrint()117 public static boolean systemSupportsPrint() { 118 // Supported on Android 4.4 or later. 119 return Build.VERSION.SDK_INT >= 19; 120 } 121 122 /** 123 * Interface implemented by classes that support printing 124 */ 125 interface PrintHelperVersionImpl { 126 setScaleMode(int scaleMode)127 void setScaleMode(int scaleMode); 128 getScaleMode()129 int getScaleMode(); 130 setColorMode(int colorMode)131 void setColorMode(int colorMode); 132 getColorMode()133 int getColorMode(); 134 setOrientation(int orientation)135 void setOrientation(int orientation); 136 getOrientation()137 int getOrientation(); 138 printBitmap(String jobName, Bitmap bitmap, OnPrintFinishCallback callback)139 void printBitmap(String jobName, Bitmap bitmap, OnPrintFinishCallback callback); 140 printBitmap(String jobName, Uri imageFile, OnPrintFinishCallback callback)141 void printBitmap(String jobName, Uri imageFile, OnPrintFinishCallback callback) 142 throws FileNotFoundException; 143 } 144 145 /** 146 * Implementation used when we do not support printing 147 */ 148 private static final class PrintHelperStub implements PrintHelperVersionImpl { 149 @ScaleMode int mScaleMode = SCALE_MODE_FILL; 150 @ColorMode int mColorMode = COLOR_MODE_COLOR; 151 @Orientation int mOrientation = ORIENTATION_LANDSCAPE; 152 153 @Override setScaleMode(@caleMode int scaleMode)154 public void setScaleMode(@ScaleMode int scaleMode) { 155 mScaleMode = scaleMode; 156 } 157 158 @ScaleMode 159 @Override getScaleMode()160 public int getScaleMode() { 161 return mScaleMode; 162 } 163 164 @ColorMode 165 @Override getColorMode()166 public int getColorMode() { 167 return mColorMode; 168 } 169 170 @Override setColorMode(@olorMode int colorMode)171 public void setColorMode(@ColorMode int colorMode) { 172 mColorMode = colorMode; 173 } 174 175 @Override setOrientation(@rientation int orientation)176 public void setOrientation(@Orientation int orientation) { 177 mOrientation = orientation; 178 } 179 180 @Orientation 181 @Override getOrientation()182 public int getOrientation() { 183 return mOrientation; 184 } 185 186 @Override printBitmap(String jobName, Bitmap bitmap, OnPrintFinishCallback callback)187 public void printBitmap(String jobName, Bitmap bitmap, OnPrintFinishCallback callback) { 188 } 189 190 @Override printBitmap(String jobName, Uri imageFile, OnPrintFinishCallback callback)191 public void printBitmap(String jobName, Uri imageFile, OnPrintFinishCallback callback) { 192 } 193 } 194 195 /** 196 * Kitkat specific PrintManager API implementation. 197 */ 198 @RequiresApi(19) 199 private static class PrintHelperApi19 implements PrintHelperVersionImpl{ 200 private static final String LOG_TAG = "PrintHelperApi19"; 201 // will be <= 300 dpi on A4 (8.3×11.7) paper (worst case of 150 dpi) 202 private static final int MAX_PRINT_SIZE = 3500; 203 final Context mContext; 204 BitmapFactory.Options mDecodeOptions = null; 205 private final Object mLock = new Object(); 206 207 /** 208 * Whether the PrintActivity respects the suggested orientation 209 */ 210 protected boolean mPrintActivityRespectsOrientation; 211 212 /** 213 * Whether the print subsystem handles min margins correctly. If not the print helper needs 214 * to fake this. 215 */ 216 protected boolean mIsMinMarginsHandlingCorrect; 217 218 @ScaleMode int mScaleMode = SCALE_MODE_FILL; 219 220 @ColorMode int mColorMode = COLOR_MODE_COLOR; 221 222 @Orientation int mOrientation; 223 PrintHelperApi19(Context context)224 PrintHelperApi19(Context context) { 225 mPrintActivityRespectsOrientation = true; 226 mIsMinMarginsHandlingCorrect = true; 227 228 mContext = context; 229 } 230 231 /** 232 * Selects whether the image will fill the paper and be cropped 233 * <p/> 234 * {@link #SCALE_MODE_FIT} 235 * or whether the image will be scaled but leave white space 236 * {@link #SCALE_MODE_FILL}. 237 * 238 * @param scaleMode {@link #SCALE_MODE_FIT} or 239 * {@link #SCALE_MODE_FILL} 240 */ 241 @Override setScaleMode(@caleMode int scaleMode)242 public void setScaleMode(@ScaleMode int scaleMode) { 243 mScaleMode = scaleMode; 244 } 245 246 /** 247 * Returns the scale mode with which the image will fill the paper. 248 * 249 * @return The scale Mode: {@link #SCALE_MODE_FIT} or 250 * {@link #SCALE_MODE_FILL} 251 */ 252 @ScaleMode 253 @Override getScaleMode()254 public int getScaleMode() { 255 return mScaleMode; 256 } 257 258 /** 259 * Sets whether the image will be printed in color (default) 260 * {@link #COLOR_MODE_COLOR} or in back and white 261 * {@link #COLOR_MODE_MONOCHROME}. 262 * 263 * @param colorMode The color mode which is one of 264 * {@link #COLOR_MODE_COLOR} and {@link #COLOR_MODE_MONOCHROME}. 265 */ 266 @Override setColorMode(@olorMode int colorMode)267 public void setColorMode(@ColorMode int colorMode) { 268 mColorMode = colorMode; 269 } 270 271 /** 272 * Sets whether to select landscape (default), {@link #ORIENTATION_LANDSCAPE} 273 * or portrait {@link #ORIENTATION_PORTRAIT} 274 * @param orientation The page orientation which is one of 275 * {@link #ORIENTATION_LANDSCAPE} or {@link #ORIENTATION_PORTRAIT}. 276 */ 277 @Override setOrientation(@rientation int orientation)278 public void setOrientation(@Orientation int orientation) { 279 mOrientation = orientation; 280 } 281 282 /** 283 * Gets the page orientation with which the image will be printed. 284 * 285 * @return The preferred orientation which is one of 286 * {@link #ORIENTATION_LANDSCAPE} or {@link #ORIENTATION_PORTRAIT} 287 */ 288 @Orientation 289 @Override getOrientation()290 public int getOrientation() { 291 /// Unset defaults to landscape but might turn image 292 if (mOrientation == 0) { 293 return ORIENTATION_LANDSCAPE; 294 } 295 return mOrientation; 296 } 297 298 /** 299 * Gets the color mode with which the image will be printed. 300 * 301 * @return The color mode which is one of {@link #COLOR_MODE_COLOR} 302 * and {@link #COLOR_MODE_MONOCHROME}. 303 */ 304 @ColorMode 305 @Override getColorMode()306 public int getColorMode() { 307 return mColorMode; 308 } 309 310 /** 311 * Check if the supplied bitmap should best be printed on a portrait orientation paper. 312 * 313 * @param bitmap The bitmap to be printed. 314 * @return true iff the picture should best be printed on a portrait orientation paper. 315 */ isPortrait(Bitmap bitmap)316 private static boolean isPortrait(Bitmap bitmap) { 317 return bitmap.getWidth() <= bitmap.getHeight(); 318 } 319 320 /** 321 * Create a build with a copy from the other print attributes. 322 * 323 * @param other The other print attributes 324 * 325 * @return A builder that will build print attributes that match the other attributes 326 */ copyAttributes(PrintAttributes other)327 protected PrintAttributes.Builder copyAttributes(PrintAttributes other) { 328 PrintAttributes.Builder b = (new PrintAttributes.Builder()) 329 .setMediaSize(other.getMediaSize()) 330 .setResolution(other.getResolution()) 331 .setMinMargins(other.getMinMargins()); 332 333 if (other.getColorMode() != 0) { 334 b.setColorMode(other.getColorMode()); 335 } 336 337 return b; 338 } 339 340 /** 341 * Prints a bitmap. 342 * 343 * @param jobName The print job name. 344 * @param bitmap The bitmap to print. 345 * @param callback Optional callback to observe when printing is finished. 346 */ 347 @Override printBitmap(final String jobName, final Bitmap bitmap, final OnPrintFinishCallback callback)348 public void printBitmap(final String jobName, final Bitmap bitmap, 349 final OnPrintFinishCallback callback) { 350 if (bitmap == null) { 351 return; 352 } 353 354 final int fittingMode = mScaleMode; // grab the fitting mode at time of call 355 PrintManager printManager = 356 (PrintManager) mContext.getSystemService(Context.PRINT_SERVICE); 357 PrintAttributes.MediaSize mediaSize; 358 if (isPortrait(bitmap)) { 359 mediaSize = PrintAttributes.MediaSize.UNKNOWN_PORTRAIT; 360 } else { 361 mediaSize = PrintAttributes.MediaSize.UNKNOWN_LANDSCAPE; 362 } 363 PrintAttributes attr = new PrintAttributes.Builder() 364 .setMediaSize(mediaSize) 365 .setColorMode(mColorMode) 366 .build(); 367 368 printManager.print(jobName, 369 new PrintDocumentAdapter() { 370 private PrintAttributes mAttributes; 371 372 @Override 373 public void onLayout(PrintAttributes oldPrintAttributes, 374 PrintAttributes newPrintAttributes, 375 CancellationSignal cancellationSignal, 376 LayoutResultCallback layoutResultCallback, 377 Bundle bundle) { 378 379 mAttributes = newPrintAttributes; 380 381 PrintDocumentInfo info = new PrintDocumentInfo.Builder(jobName) 382 .setContentType(PrintDocumentInfo.CONTENT_TYPE_PHOTO) 383 .setPageCount(1) 384 .build(); 385 boolean changed = !newPrintAttributes.equals(oldPrintAttributes); 386 layoutResultCallback.onLayoutFinished(info, changed); 387 } 388 389 @Override 390 public void onWrite(PageRange[] pageRanges, 391 ParcelFileDescriptor fileDescriptor, 392 CancellationSignal cancellationSignal, 393 WriteResultCallback writeResultCallback) { 394 writeBitmap(mAttributes, fittingMode, bitmap, fileDescriptor, 395 cancellationSignal, writeResultCallback); 396 } 397 398 @Override 399 public void onFinish() { 400 if (callback != null) { 401 callback.onFinish(); 402 } 403 } 404 }, attr); 405 } 406 407 /** 408 * Calculates the transform the print an Image to fill the page 409 * 410 * @param imageWidth with of bitmap 411 * @param imageHeight height of bitmap 412 * @param content The output page dimensions 413 * @param fittingMode The mode of fitting {@link #SCALE_MODE_FILL} vs 414 * {@link #SCALE_MODE_FIT} 415 * @return Matrix to be used in canvas.drawBitmap(bitmap, matrix, null) call 416 */ getMatrix(int imageWidth, int imageHeight, RectF content, @ScaleMode int fittingMode)417 private Matrix getMatrix(int imageWidth, int imageHeight, RectF content, 418 @ScaleMode int fittingMode) { 419 Matrix matrix = new Matrix(); 420 421 // Compute and apply scale to fill the page. 422 float scale = content.width() / imageWidth; 423 if (fittingMode == SCALE_MODE_FILL) { 424 scale = Math.max(scale, content.height() / imageHeight); 425 } else { 426 scale = Math.min(scale, content.height() / imageHeight); 427 } 428 matrix.postScale(scale, scale); 429 430 // Center the content. 431 final float translateX = (content.width() 432 - imageWidth * scale) / 2; 433 final float translateY = (content.height() 434 - imageHeight * scale) / 2; 435 matrix.postTranslate(translateX, translateY); 436 return matrix; 437 } 438 439 /** 440 * Write a bitmap for a PDF document. 441 * 442 * @param attributes The print attributes 443 * @param fittingMode How to fit the bitmap 444 * @param bitmap The bitmap to write 445 * @param fileDescriptor The file to write to 446 * @param cancellationSignal Signal cancelling operation 447 * @param writeResultCallback Callback to call once written 448 */ writeBitmap(final PrintAttributes attributes, final int fittingMode, final Bitmap bitmap, final ParcelFileDescriptor fileDescriptor, final CancellationSignal cancellationSignal, final PrintDocumentAdapter.WriteResultCallback writeResultCallback)449 private void writeBitmap(final PrintAttributes attributes, final int fittingMode, 450 final Bitmap bitmap, final ParcelFileDescriptor fileDescriptor, 451 final CancellationSignal cancellationSignal, 452 final PrintDocumentAdapter.WriteResultCallback writeResultCallback) { 453 final PrintAttributes pdfAttributes; 454 if (mIsMinMarginsHandlingCorrect) { 455 pdfAttributes = attributes; 456 } else { 457 // If the handling of any margin != 0 is broken, strip the margins and add them to 458 // the bitmap later 459 pdfAttributes = copyAttributes(attributes) 460 .setMinMargins(new PrintAttributes.Margins(0, 0, 0, 0)).build(); 461 } 462 463 (new AsyncTask<Void, Void, Throwable>() { 464 @Override 465 protected Throwable doInBackground(Void... params) { 466 try { 467 if (cancellationSignal.isCanceled()) { 468 return null; 469 } 470 471 PrintedPdfDocument pdfDocument = new PrintedPdfDocument(mContext, 472 pdfAttributes); 473 474 Bitmap maybeGrayscale = convertBitmapForColorMode(bitmap, 475 pdfAttributes.getColorMode()); 476 477 if (cancellationSignal.isCanceled()) { 478 return null; 479 } 480 481 try { 482 PdfDocument.Page page = pdfDocument.startPage(1); 483 484 RectF contentRect; 485 if (mIsMinMarginsHandlingCorrect) { 486 contentRect = new RectF(page.getInfo().getContentRect()); 487 } else { 488 // Create dummy doc that has the margins to compute correctly sized 489 // content rectangle 490 PrintedPdfDocument dummyDocument = new PrintedPdfDocument(mContext, 491 attributes); 492 PdfDocument.Page dummyPage = dummyDocument.startPage(1); 493 contentRect = new RectF(dummyPage.getInfo().getContentRect()); 494 dummyDocument.finishPage(dummyPage); 495 dummyDocument.close(); 496 } 497 498 // Resize bitmap 499 Matrix matrix = getMatrix( 500 maybeGrayscale.getWidth(), maybeGrayscale.getHeight(), 501 contentRect, fittingMode); 502 503 if (mIsMinMarginsHandlingCorrect) { 504 // The pdfDocument takes care of the positioning and margins 505 } else { 506 // Move it to the correct position. 507 matrix.postTranslate(contentRect.left, contentRect.top); 508 509 // Cut off margins 510 page.getCanvas().clipRect(contentRect); 511 } 512 513 // Draw the bitmap. 514 page.getCanvas().drawBitmap(maybeGrayscale, matrix, null); 515 516 // Finish the page. 517 pdfDocument.finishPage(page); 518 519 if (cancellationSignal.isCanceled()) { 520 return null; 521 } 522 523 // Write the document. 524 pdfDocument.writeTo( 525 new FileOutputStream(fileDescriptor.getFileDescriptor())); 526 return null; 527 } finally { 528 pdfDocument.close(); 529 530 if (fileDescriptor != null) { 531 try { 532 fileDescriptor.close(); 533 } catch (IOException ioe) { 534 // ignore 535 } 536 } 537 // If we created a new instance for grayscaling, then recycle it here. 538 if (maybeGrayscale != bitmap) { 539 maybeGrayscale.recycle(); 540 } 541 } 542 } catch (Throwable t) { 543 return t; 544 } 545 } 546 547 @Override 548 protected void onPostExecute(Throwable throwable) { 549 if (cancellationSignal.isCanceled()) { 550 // Cancelled. 551 writeResultCallback.onWriteCancelled(); 552 } else if (throwable == null) { 553 // Done. 554 writeResultCallback.onWriteFinished( 555 new PageRange[] { PageRange.ALL_PAGES }); 556 } else { 557 // Failed. 558 Log.e(LOG_TAG, "Error writing printed content", throwable); 559 writeResultCallback.onWriteFailed(null); 560 } 561 } 562 }).execute(); 563 } 564 565 /** 566 * Prints an image located at the Uri. Image types supported are those of 567 * <code>BitmapFactory.decodeStream</code> (JPEG, GIF, PNG, BMP, WEBP) 568 * 569 * @param jobName The print job name. 570 * @param imageFile The <code>Uri</code> pointing to an image to print. 571 * @param callback Optional callback to observe when printing is finished. 572 * @throws FileNotFoundException if <code>Uri</code> is not pointing to a valid image. 573 */ 574 @Override printBitmap(final String jobName, final Uri imageFile, final OnPrintFinishCallback callback)575 public void printBitmap(final String jobName, final Uri imageFile, 576 final OnPrintFinishCallback callback) 577 throws FileNotFoundException { 578 final int fittingMode = mScaleMode; 579 580 PrintDocumentAdapter printDocumentAdapter = new PrintDocumentAdapter() { 581 private PrintAttributes mAttributes; 582 AsyncTask<Uri, Boolean, Bitmap> mLoadBitmap; 583 Bitmap mBitmap = null; 584 585 @Override 586 public void onLayout(final PrintAttributes oldPrintAttributes, 587 final PrintAttributes newPrintAttributes, 588 final CancellationSignal cancellationSignal, 589 final LayoutResultCallback layoutResultCallback, 590 Bundle bundle) { 591 592 synchronized (this) { 593 mAttributes = newPrintAttributes; 594 } 595 596 if (cancellationSignal.isCanceled()) { 597 layoutResultCallback.onLayoutCancelled(); 598 return; 599 } 600 // we finished the load 601 if (mBitmap != null) { 602 PrintDocumentInfo info = new PrintDocumentInfo.Builder(jobName) 603 .setContentType(PrintDocumentInfo.CONTENT_TYPE_PHOTO) 604 .setPageCount(1) 605 .build(); 606 boolean changed = !newPrintAttributes.equals(oldPrintAttributes); 607 layoutResultCallback.onLayoutFinished(info, changed); 608 return; 609 } 610 611 mLoadBitmap = new AsyncTask<Uri, Boolean, Bitmap>() { 612 @Override 613 protected void onPreExecute() { 614 // First register for cancellation requests. 615 cancellationSignal.setOnCancelListener( 616 new CancellationSignal.OnCancelListener() { 617 @Override 618 public void onCancel() { // on different thread 619 cancelLoad(); 620 cancel(false); 621 } 622 }); 623 } 624 625 @Override 626 protected Bitmap doInBackground(Uri... uris) { 627 try { 628 return loadConstrainedBitmap(imageFile); 629 } catch (FileNotFoundException e) { 630 /* ignore */ 631 } 632 return null; 633 } 634 635 @Override 636 protected void onPostExecute(Bitmap bitmap) { 637 super.onPostExecute(bitmap); 638 639 // If orientation was not set by the caller, try to fit the bitmap on 640 // the current paper by potentially rotating the bitmap by 90 degrees. 641 if (bitmap != null 642 && (!mPrintActivityRespectsOrientation || mOrientation == 0)) { 643 PrintAttributes.MediaSize mediaSize; 644 645 synchronized (this) { 646 mediaSize = mAttributes.getMediaSize(); 647 } 648 649 if (mediaSize != null) { 650 if (mediaSize.isPortrait() != isPortrait(bitmap)) { 651 Matrix rotation = new Matrix(); 652 653 rotation.postRotate(90); 654 bitmap = Bitmap.createBitmap(bitmap, 0, 0, 655 bitmap.getWidth(), bitmap.getHeight(), rotation, 656 true); 657 } 658 } 659 } 660 661 mBitmap = bitmap; 662 if (bitmap != null) { 663 PrintDocumentInfo info = new PrintDocumentInfo.Builder(jobName) 664 .setContentType(PrintDocumentInfo.CONTENT_TYPE_PHOTO) 665 .setPageCount(1) 666 .build(); 667 668 boolean changed = !newPrintAttributes.equals(oldPrintAttributes); 669 670 layoutResultCallback.onLayoutFinished(info, changed); 671 672 } else { 673 layoutResultCallback.onLayoutFailed(null); 674 } 675 mLoadBitmap = null; 676 } 677 678 @Override 679 protected void onCancelled(Bitmap result) { 680 // Task was cancelled, report that. 681 layoutResultCallback.onLayoutCancelled(); 682 mLoadBitmap = null; 683 } 684 }.execute(); 685 } 686 687 private void cancelLoad() { 688 synchronized (mLock) { // prevent race with set null below 689 if (mDecodeOptions != null) { 690 mDecodeOptions.requestCancelDecode(); 691 mDecodeOptions = null; 692 } 693 } 694 } 695 696 @Override 697 public void onFinish() { 698 super.onFinish(); 699 cancelLoad(); 700 if (mLoadBitmap != null) { 701 mLoadBitmap.cancel(true); 702 } 703 if (callback != null) { 704 callback.onFinish(); 705 } 706 if (mBitmap != null) { 707 mBitmap.recycle(); 708 mBitmap = null; 709 } 710 } 711 712 @Override 713 public void onWrite(PageRange[] pageRanges, ParcelFileDescriptor fileDescriptor, 714 CancellationSignal cancellationSignal, 715 WriteResultCallback writeResultCallback) { 716 writeBitmap(mAttributes, fittingMode, mBitmap, fileDescriptor, 717 cancellationSignal, writeResultCallback); 718 } 719 }; 720 721 PrintManager printManager = 722 (PrintManager) mContext.getSystemService(Context.PRINT_SERVICE); 723 PrintAttributes.Builder builder = new PrintAttributes.Builder(); 724 builder.setColorMode(mColorMode); 725 726 if (mOrientation == ORIENTATION_LANDSCAPE || mOrientation == 0) { 727 builder.setMediaSize(PrintAttributes.MediaSize.UNKNOWN_LANDSCAPE); 728 } else if (mOrientation == ORIENTATION_PORTRAIT) { 729 builder.setMediaSize(PrintAttributes.MediaSize.UNKNOWN_PORTRAIT); 730 } 731 PrintAttributes attr = builder.build(); 732 733 printManager.print(jobName, printDocumentAdapter, attr); 734 } 735 736 /** 737 * Loads a bitmap while limiting its size 738 * 739 * @param uri location of a valid image 740 * @return the Bitmap 741 * @throws FileNotFoundException if the Uri does not point to an image 742 */ loadConstrainedBitmap(Uri uri)743 private Bitmap loadConstrainedBitmap(Uri uri) 744 throws FileNotFoundException { 745 if (uri == null || mContext == null) { 746 throw new IllegalArgumentException("bad argument to getScaledBitmap"); 747 } 748 // Get width and height of stored bitmap 749 BitmapFactory.Options opt = new BitmapFactory.Options(); 750 opt.inJustDecodeBounds = true; 751 loadBitmap(uri, opt); 752 753 int w = opt.outWidth; 754 int h = opt.outHeight; 755 756 // If bitmap cannot be decoded, return null 757 if (w <= 0 || h <= 0) { 758 return null; 759 } 760 761 // Find best downsampling size 762 int imageSide = Math.max(w, h); 763 764 int sampleSize = 1; 765 while (imageSide > MAX_PRINT_SIZE) { 766 imageSide >>>= 1; 767 sampleSize <<= 1; 768 } 769 770 // Make sure sample size is reasonable 771 if (sampleSize <= 0 || 0 >= (Math.min(w, h) / sampleSize)) { 772 return null; 773 } 774 BitmapFactory.Options decodeOptions; 775 synchronized (mLock) { // prevent race with set null below 776 mDecodeOptions = new BitmapFactory.Options(); 777 mDecodeOptions.inMutable = true; 778 mDecodeOptions.inSampleSize = sampleSize; 779 decodeOptions = mDecodeOptions; 780 } 781 try { 782 return loadBitmap(uri, decodeOptions); 783 } finally { 784 synchronized (mLock) { 785 mDecodeOptions = null; 786 } 787 } 788 } 789 790 /** 791 * Returns the bitmap from the given uri loaded using the given options. 792 * Returns null on failure. 793 */ loadBitmap(Uri uri, BitmapFactory.Options o)794 private Bitmap loadBitmap(Uri uri, BitmapFactory.Options o) throws FileNotFoundException { 795 if (uri == null || mContext == null) { 796 throw new IllegalArgumentException("bad argument to loadBitmap"); 797 } 798 InputStream is = null; 799 try { 800 is = mContext.getContentResolver().openInputStream(uri); 801 return BitmapFactory.decodeStream(is, null, o); 802 } finally { 803 if (is != null) { 804 try { 805 is.close(); 806 } catch (IOException t) { 807 Log.w(LOG_TAG, "close fail ", t); 808 } 809 } 810 } 811 } 812 convertBitmapForColorMode(Bitmap original, @ColorMode int colorMode)813 private Bitmap convertBitmapForColorMode(Bitmap original, @ColorMode int colorMode) { 814 if (colorMode != COLOR_MODE_MONOCHROME) { 815 return original; 816 } 817 // Create a grayscale bitmap 818 Bitmap grayscale = Bitmap.createBitmap(original.getWidth(), original.getHeight(), 819 Bitmap.Config.ARGB_8888); 820 Canvas c = new Canvas(grayscale); 821 Paint p = new Paint(); 822 ColorMatrix cm = new ColorMatrix(); 823 cm.setSaturation(0); 824 ColorMatrixColorFilter f = new ColorMatrixColorFilter(cm); 825 p.setColorFilter(f); 826 c.drawBitmap(original, 0, 0, p); 827 c.setBitmap(null); 828 829 return grayscale; 830 } 831 } 832 833 /** 834 * Api20 specific PrintManager API implementation. 835 */ 836 @RequiresApi(20) 837 private static class PrintHelperApi20 extends PrintHelperApi19 { PrintHelperApi20(Context context)838 PrintHelperApi20(Context context) { 839 super(context); 840 841 842 // There is a bug in the PrintActivity that causes it to ignore the orientation 843 mPrintActivityRespectsOrientation = false; 844 } 845 } 846 847 /** 848 * Api23 specific PrintManager API implementation. 849 */ 850 @RequiresApi(23) 851 private static class PrintHelperApi23 extends PrintHelperApi20 { 852 @Override copyAttributes(PrintAttributes other)853 protected PrintAttributes.Builder copyAttributes(PrintAttributes other) { 854 PrintAttributes.Builder b = super.copyAttributes(other); 855 856 if (other.getDuplexMode() != 0) { 857 b.setDuplexMode(other.getDuplexMode()); 858 } 859 860 return b; 861 } 862 PrintHelperApi23(Context context)863 PrintHelperApi23(Context context) { 864 super(context); 865 866 mIsMinMarginsHandlingCorrect = false; 867 } 868 } 869 870 /** 871 * Api24 specific PrintManager API implementation. 872 */ 873 @RequiresApi(24) 874 private static class PrintHelperApi24 extends PrintHelperApi23 { PrintHelperApi24(Context context)875 PrintHelperApi24(Context context) { 876 super(context); 877 878 mIsMinMarginsHandlingCorrect = true; 879 mPrintActivityRespectsOrientation = true; 880 } 881 } 882 883 /** 884 * Constructs the PrintHelper that can be used to print images. 885 * 886 * @param context A context for accessing system resources. 887 */ PrintHelper(Context context)888 public PrintHelper(Context context) { 889 if (Build.VERSION.SDK_INT >= 24) { 890 mImpl = new PrintHelperApi24(context); 891 } else if (Build.VERSION.SDK_INT >= 23) { 892 mImpl = new PrintHelperApi23(context); 893 } else if (Build.VERSION.SDK_INT >= 20) { 894 mImpl = new PrintHelperApi20(context); 895 } else if (Build.VERSION.SDK_INT >= 19) { 896 mImpl = new PrintHelperApi19(context); 897 } else { 898 // System does not support printing. 899 mImpl = new PrintHelperStub(); 900 } 901 } 902 903 /** 904 * Selects whether the image will fill the paper and be cropped 905 * {@link #SCALE_MODE_FIT} 906 * or whether the image will be scaled but leave white space 907 * {@link #SCALE_MODE_FILL}. 908 * 909 * @param scaleMode {@link #SCALE_MODE_FIT} or 910 * {@link #SCALE_MODE_FILL} 911 */ setScaleMode(@caleMode int scaleMode)912 public void setScaleMode(@ScaleMode int scaleMode) { 913 mImpl.setScaleMode(scaleMode); 914 } 915 916 /** 917 * Returns the scale mode with which the image will fill the paper. 918 * 919 * @return The scale Mode: {@link #SCALE_MODE_FIT} or 920 * {@link #SCALE_MODE_FILL} 921 */ 922 @ScaleMode getScaleMode()923 public int getScaleMode() { 924 return mImpl.getScaleMode(); 925 } 926 927 /** 928 * Sets whether the image will be printed in color (default) 929 * {@link #COLOR_MODE_COLOR} or in back and white 930 * {@link #COLOR_MODE_MONOCHROME}. 931 * 932 * @param colorMode The color mode which is one of 933 * {@link #COLOR_MODE_COLOR} and {@link #COLOR_MODE_MONOCHROME}. 934 */ setColorMode(@olorMode int colorMode)935 public void setColorMode(@ColorMode int colorMode) { 936 mImpl.setColorMode(colorMode); 937 } 938 939 /** 940 * Gets the color mode with which the image will be printed. 941 * 942 * @return The color mode which is one of {@link #COLOR_MODE_COLOR} 943 * and {@link #COLOR_MODE_MONOCHROME}. 944 */ 945 @ColorMode getColorMode()946 public int getColorMode() { 947 return mImpl.getColorMode(); 948 } 949 950 /** 951 * Sets whether the image will be printed in landscape {@link #ORIENTATION_LANDSCAPE} (default) 952 * or portrait {@link #ORIENTATION_PORTRAIT}. 953 * 954 * @param orientation The page orientation which is one of 955 * {@link #ORIENTATION_LANDSCAPE} or {@link #ORIENTATION_PORTRAIT}. 956 */ setOrientation(int orientation)957 public void setOrientation(int orientation) { 958 mImpl.setOrientation(orientation); 959 } 960 961 /** 962 * Gets whether the image will be printed in landscape or portrait. 963 * 964 * @return The page orientation which is one of 965 * {@link #ORIENTATION_LANDSCAPE} or {@link #ORIENTATION_PORTRAIT}. 966 */ getOrientation()967 public int getOrientation() { 968 return mImpl.getOrientation(); 969 } 970 971 972 /** 973 * Prints a bitmap. 974 * 975 * @param jobName The print job name. 976 * @param bitmap The bitmap to print. 977 */ printBitmap(String jobName, Bitmap bitmap)978 public void printBitmap(String jobName, Bitmap bitmap) { 979 mImpl.printBitmap(jobName, bitmap, null); 980 } 981 982 /** 983 * Prints a bitmap. 984 * 985 * @param jobName The print job name. 986 * @param bitmap The bitmap to print. 987 * @param callback Optional callback to observe when printing is finished. 988 */ printBitmap(String jobName, Bitmap bitmap, OnPrintFinishCallback callback)989 public void printBitmap(String jobName, Bitmap bitmap, OnPrintFinishCallback callback) { 990 mImpl.printBitmap(jobName, bitmap, callback); 991 } 992 993 /** 994 * Prints an image located at the Uri. Image types supported are those of 995 * {@link android.graphics.BitmapFactory#decodeStream(java.io.InputStream) 996 * android.graphics.BitmapFactory.decodeStream(java.io.InputStream)} 997 * 998 * @param jobName The print job name. 999 * @param imageFile The <code>Uri</code> pointing to an image to print. 1000 * @throws FileNotFoundException if <code>Uri</code> is not pointing to a valid image. 1001 */ printBitmap(String jobName, Uri imageFile)1002 public void printBitmap(String jobName, Uri imageFile) throws FileNotFoundException { 1003 mImpl.printBitmap(jobName, imageFile, null); 1004 } 1005 1006 /** 1007 * Prints an image located at the Uri. Image types supported are those of 1008 * {@link android.graphics.BitmapFactory#decodeStream(java.io.InputStream) 1009 * android.graphics.BitmapFactory.decodeStream(java.io.InputStream)} 1010 * 1011 * @param jobName The print job name. 1012 * @param imageFile The <code>Uri</code> pointing to an image to print. 1013 * @throws FileNotFoundException if <code>Uri</code> is not pointing to a valid image. 1014 * @param callback Optional callback to observe when printing is finished. 1015 */ printBitmap(String jobName, Uri imageFile, OnPrintFinishCallback callback)1016 public void printBitmap(String jobName, Uri imageFile, OnPrintFinishCallback callback) 1017 throws FileNotFoundException { 1018 mImpl.printBitmap(jobName, imageFile, callback); 1019 } 1020 } 1021