• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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