add Async handling of Print

bug:11322643
Change-Id: I9f1272f4d96752d9ce817a56f2e3e7766b82f989
diff --git a/v4/java/android/support/v4/print/PrintHelper.java b/v4/java/android/support/v4/print/PrintHelper.java
index 0d3c5ba..5e3ef3c 100644
--- a/v4/java/android/support/v4/print/PrintHelper.java
+++ b/v4/java/android/support/v4/print/PrintHelper.java
@@ -47,6 +47,16 @@
      */
     public static final int COLOR_MODE_COLOR = 2;
 
+    /**
+     * Print the image in landscape orientation (default).
+     */
+    public static final int ORIENTATION_LANDSCAPE = 1;
+
+    /**
+     * Print the image in  portrait orientation.
+     */
+    public static final int ORIENTATION_PORTRAIT = 2;
+
     PrintHelperVersionImpl mImpl;
 
     /**
@@ -75,6 +85,10 @@
 
         public int getColorMode();
 
+        public void setOrientation(int orientation);
+
+        public int getOrientation();
+
         public void printBitmap(String jobName, Bitmap bitmap);
 
         public void printBitmap(String jobName, Uri imageFile)
@@ -87,7 +101,7 @@
     private static final class PrintHelperStubImpl implements PrintHelperVersionImpl {
         int mScaleMode = SCALE_MODE_FILL;
         int mColorMode = COLOR_MODE_COLOR;
-
+        int mOrientation = ORIENTATION_LANDSCAPE;
         @Override
         public void setScaleMode(int scaleMode) {
             mScaleMode = scaleMode;
@@ -104,6 +118,12 @@
         }
 
         @Override
+        public void setOrientation(int orientation) { mOrientation = orientation; }
+
+        @Override
+        public int getOrientation() { return mOrientation; }
+
+        @Override
         public int getScaleMode() {
             return mScaleMode;
         }
@@ -121,40 +141,50 @@
      * Implementation used on KitKat (and above)
      */
     private static final class PrintHelperKitkatImpl implements PrintHelperVersionImpl {
-        private final PrintHelperKitkat printHelper;
+        private final PrintHelperKitkat mPrintHelper;
 
         PrintHelperKitkatImpl(Context context) {
-            printHelper = new PrintHelperKitkat(context);
+            mPrintHelper = new PrintHelperKitkat(context);
         }
 
         @Override
         public void setScaleMode(int scaleMode) {
-            printHelper.setScaleMode(scaleMode);
+            mPrintHelper.setScaleMode(scaleMode);
         }
 
         @Override
         public int getScaleMode() {
-            return printHelper.getScaleMode();
+            return mPrintHelper.getScaleMode();
         }
 
         @Override
         public void setColorMode(int colorMode) {
-            printHelper.setColorMode(colorMode);
+            mPrintHelper.setColorMode(colorMode);
         }
 
         @Override
         public int getColorMode() {
-            return printHelper.getColorMode();
+            return mPrintHelper.getColorMode();
+        }
+
+        @Override
+        public void setOrientation(int orientation) {
+            mPrintHelper.setOrientation(orientation);
+        }
+
+        @Override
+        public int getOrientation() {
+            return mPrintHelper.getOrientation();
         }
 
         @Override
         public void printBitmap(String jobName, Bitmap bitmap) {
-            printHelper.printBitmap(jobName, bitmap);
+            mPrintHelper.printBitmap(jobName, bitmap);
         }
 
         @Override
         public void printBitmap(String jobName, Uri imageFile) throws FileNotFoundException {
-            printHelper.printBitmap(jobName, imageFile);
+            mPrintHelper.printBitmap(jobName, imageFile);
         }
     }
 
@@ -218,6 +248,27 @@
     }
 
     /**
+     * Sets whether the image will be printed in landscape {@link #ORIENTATION_LANDSCAPE} (default)
+     * or portrait {@link #ORIENTATION_PORTRAIT}.
+     *
+     * @param orientation The page orientation which is one of
+     *                    {@link #ORIENTATION_LANDSCAPE} or {@link #ORIENTATION_PORTRAIT}.
+     */
+    public void setOrientation(int orientation) {
+        mImpl.setOrientation(orientation);
+    }
+
+    /**
+     * Gets whether the image will be printed in landscape or portrait.
+     *
+     * @return The page orientation which is one of
+     * {@link #ORIENTATION_LANDSCAPE} or {@link #ORIENTATION_PORTRAIT}.
+     */
+    public int getOrientation() {
+        return mImpl.getOrientation();
+    }
+
+    /**
      * Prints a bitmap.
      *
      * @param jobName The print job name.
diff --git a/v4/kitkat/android/support/v4/print/PrintHelperKitkat.java b/v4/kitkat/android/support/v4/print/PrintHelperKitkat.java
index 0786763..89cc9e7 100644
--- a/v4/kitkat/android/support/v4/print/PrintHelperKitkat.java
+++ b/v4/kitkat/android/support/v4/print/PrintHelperKitkat.java
@@ -23,6 +23,7 @@
 import android.graphics.RectF;
 import android.graphics.pdf.PdfDocument.Page;
 import android.net.Uri;
+import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.ParcelFileDescriptor;
@@ -42,11 +43,13 @@
 /**
  * Kitkat specific PrintManager API implementation.
  */
-public class PrintHelperKitkat {
+class PrintHelperKitkat {
     private static final String LOG_TAG = "PrintHelperKitkat";
     // will be <= 300 dpi on A4 (8.3×11.7) paper (worst case of 150 dpi)
     private final static int MAX_PRINT_SIZE = 3500;
     final Context mContext;
+    BitmapFactory.Options mDecodeOptions = null;
+    private final Object mLock = new Object();
     /**
      * image will be scaled but leave white space
      */
@@ -57,10 +60,19 @@
     public static final int SCALE_MODE_FILL = 2;
 
     /**
+     * select landscape (default)
+     */
+    public static final int ORIENTATION_LANDSCAPE = 1;
+
+    /**
+     * select portrait
+     */
+    public static final int ORIENTATION_PORTRAIT = 2;
+
+    /**
      * this is a black and white image
      */
     public static final int COLOR_MODE_MONOCHROME = 1;
-
     /**
      * this is a color image (default)
      */
@@ -70,12 +82,15 @@
 
     int mColorMode = COLOR_MODE_COLOR;
 
+    int mOrientation = ORIENTATION_LANDSCAPE;
+
     PrintHelperKitkat(Context context) {
         mContext = context;
     }
 
     /**
      * Selects whether the image will fill the paper and be cropped
+     * <p/>
      * {@link #SCALE_MODE_FIT}
      * or whether the image will be scaled but leave white space
      * {@link #SCALE_MODE_FILL}.
@@ -103,13 +118,33 @@
      * {@link #COLOR_MODE_MONOCHROME}.
      *
      * @param colorMode The color mode which is one of
-     * {@link #COLOR_MODE_COLOR} and {@link #COLOR_MODE_MONOCHROME}.
+     *                  {@link #COLOR_MODE_COLOR} and {@link #COLOR_MODE_MONOCHROME}.
      */
     public void setColorMode(int colorMode) {
         mColorMode = colorMode;
     }
 
     /**
+     * Sets whether to select landscape (default), {@link #ORIENTATION_LANDSCAPE}
+     * or portrait {@link #ORIENTATION_PORTRAIT}
+     * @param orientation The page orientation which is one of
+     *                    {@link #ORIENTATION_LANDSCAPE} or {@link #ORIENTATION_PORTRAIT}.
+     */
+    public void setOrientation(int orientation) {
+        mOrientation = orientation;
+    }
+
+    /**
+     * Gets the page orientation with which the image will be printed.
+     *
+     * @return The preferred orientation which is one of
+     * {@link #ORIENTATION_LANDSCAPE} or {@link #ORIENTATION_PORTRAIT}
+     */
+    public int getOrientation() {
+        return mOrientation;
+    }
+
+    /**
      * Gets the color mode with which the image will be printed.
      *
      * @return The color mode which is one of {@link #COLOR_MODE_COLOR}
@@ -171,23 +206,9 @@
                             Page page = pdfDocument.startPage(1);
 
                             RectF content = new RectF(page.getInfo().getContentRect());
-                            Matrix matrix = new Matrix();
 
-                            // Compute and apply scale to fill the page.
-                            float scale = content.width() / bitmap.getWidth();
-                            if (fittingMode == SCALE_MODE_FILL) {
-                                scale = Math.max(scale, content.height() / bitmap.getHeight());
-                            } else {
-                                scale = Math.min(scale, content.height() / bitmap.getHeight());
-                            }
-                            matrix.postScale(scale, scale);
-
-                            // Center the content.
-                            final float translateX = (content.width()
-                                    - bitmap.getWidth() * scale) / 2;
-                            final float translateY = (content.height()
-                                    - bitmap.getHeight() * scale) / 2;
-                            matrix.postTranslate(translateX, translateY);
+                            Matrix matrix = getMatrix(bitmap.getWidth(), bitmap.getHeight(),
+                                    content, fittingMode);
 
                             // Draw the bitmap.
                             page.getCanvas().drawBitmap(bitmap, matrix, null);
@@ -224,6 +245,36 @@
     }
 
     /**
+     * Calculates the transform the print an Image to fill the page
+     *
+     * @param imageWidth  with of bitmap
+     * @param imageHeight height of bitmap
+     * @param content     The output page dimensions
+     * @param fittingMode The mode of fitting {@link #SCALE_MODE_FILL} vs {@link #SCALE_MODE_FIT}
+     * @return Matrix to be used in canvas.drawBitmap(bitmap, matrix, null) call
+     */
+    private Matrix getMatrix(int imageWidth, int imageHeight, RectF content, int fittingMode) {
+        Matrix matrix = new Matrix();
+
+        // Compute and apply scale to fill the page.
+        float scale = content.width() / imageWidth;
+        if (fittingMode == SCALE_MODE_FILL) {
+            scale = Math.max(scale, content.height() / imageHeight);
+        } else {
+            scale = Math.min(scale, content.height() / imageHeight);
+        }
+        matrix.postScale(scale, scale);
+
+        // Center the content.
+        final float translateX = (content.width()
+                - imageWidth * scale) / 2;
+        final float translateY = (content.height()
+                - imageHeight * scale) / 2;
+        matrix.postTranslate(translateX, translateY);
+        return matrix;
+    }
+
+    /**
      * Prints an image located at the Uri. Image types supported are those of
      * <code>BitmapFactory.decodeStream</code> (JPEG, GIF, PNG, BMP, WEBP)
      *
@@ -231,9 +282,167 @@
      * @param imageFile The <code>Uri</code> pointing to an image to print.
      * @throws FileNotFoundException if <code>Uri</code> is not pointing to a valid image.
      */
-    public void printBitmap(String jobName, Uri imageFile) throws FileNotFoundException {
-        Bitmap bitmap = loadConstrainedBitmap(imageFile, MAX_PRINT_SIZE);
-        printBitmap(jobName, bitmap);
+    public void printBitmap(final String jobName, final Uri imageFile)
+            throws FileNotFoundException {
+        final int fittingMode = mScaleMode;
+
+        PrintDocumentAdapter printDocumentAdapter = new PrintDocumentAdapter() {
+            private PrintAttributes mAttributes;
+            AsyncTask<Uri, Boolean, Bitmap> loadBitmap;
+            Bitmap mBitmap = null;
+
+            @Override
+            public void onLayout(final PrintAttributes oldPrintAttributes,
+                                 final PrintAttributes newPrintAttributes,
+                                 final CancellationSignal cancellationSignal,
+                                 final LayoutResultCallback layoutResultCallback,
+                                 Bundle bundle) {
+                if (cancellationSignal.isCanceled()) {
+                    layoutResultCallback.onLayoutCancelled();
+                    mAttributes = newPrintAttributes;
+                    return;
+                }
+                // we finished the load
+                if (mBitmap != null) {
+                    PrintDocumentInfo info = new PrintDocumentInfo.Builder(jobName)
+                            .setContentType(PrintDocumentInfo.CONTENT_TYPE_PHOTO)
+                            .setPageCount(1)
+                            .build();
+                    boolean changed = !newPrintAttributes.equals(oldPrintAttributes);
+                    layoutResultCallback.onLayoutFinished(info, changed);
+                    return;
+                }
+
+                loadBitmap = new AsyncTask<Uri, Boolean, Bitmap>() {
+
+                    @Override
+                    protected void onPreExecute() {
+                        // First register for cancellation requests.
+                        cancellationSignal.setOnCancelListener(
+                                new CancellationSignal.OnCancelListener() {
+                                    @Override
+                                    public void onCancel() { // on different thread
+                                        cancelLoad();
+                                        cancel(false);
+                                    }
+                                });
+                    }
+
+                    @Override
+                    protected Bitmap doInBackground(Uri... uris) {
+                        try {
+                            return loadConstrainedBitmap(imageFile, MAX_PRINT_SIZE);
+                        } catch (FileNotFoundException e) {
+                          /* ignore */
+                        }
+                        return null;
+                    }
+
+                    @Override
+                    protected void onPostExecute(Bitmap bitmap) {
+                        super.onPostExecute(bitmap);
+                        mBitmap = bitmap;
+                        if (bitmap != null) {
+                            PrintDocumentInfo info = new PrintDocumentInfo.Builder(jobName)
+                                    .setContentType(PrintDocumentInfo.CONTENT_TYPE_PHOTO)
+                                    .setPageCount(1)
+                                    .build();
+                            boolean changed = !newPrintAttributes.equals(oldPrintAttributes);
+
+                            layoutResultCallback.onLayoutFinished(info, changed);
+
+                        } else {
+                            layoutResultCallback.onLayoutFailed(null);
+                        }
+                    }
+
+                    @Override
+                    protected void onCancelled(Bitmap result) {
+                        // Task was cancelled, report that.
+                        layoutResultCallback.onLayoutCancelled();
+                    }
+                };
+                loadBitmap.execute();
+
+                mAttributes = newPrintAttributes;
+            }
+
+            private void cancelLoad() {
+                synchronized (mLock) { // prevent race with set null below
+                    if (mDecodeOptions != null) {
+                        mDecodeOptions.requestCancelDecode();
+                        mDecodeOptions = null;
+                    }
+                }
+            }
+
+            @Override
+            public void onFinish() {
+                super.onFinish();
+                cancelLoad();
+                loadBitmap.cancel(true);
+            }
+
+            @Override
+            public void onWrite(PageRange[] pageRanges, ParcelFileDescriptor fileDescriptor,
+                                CancellationSignal cancellationSignal,
+                                WriteResultCallback writeResultCallback) {
+                PrintedPdfDocument pdfDocument = new PrintedPdfDocument(mContext,
+                        mAttributes);
+                try {
+
+                    Page page = pdfDocument.startPage(1);
+                    RectF content = new RectF(page.getInfo().getContentRect());
+
+                    // Compute and apply scale to fill the page.
+                    Matrix matrix = getMatrix(mBitmap.getWidth(), mBitmap.getHeight(),
+                            content, fittingMode);
+
+                    // Draw the bitmap.
+                    page.getCanvas().drawBitmap(mBitmap, matrix, null);
+
+                    // Finish the page.
+                    pdfDocument.finishPage(page);
+
+                    try {
+                        // Write the document.
+                        pdfDocument.writeTo(new FileOutputStream(
+                                fileDescriptor.getFileDescriptor()));
+                        // Done.
+                        writeResultCallback.onWriteFinished(
+                                new PageRange[]{PageRange.ALL_PAGES});
+                    } catch (IOException ioe) {
+                        // Failed.
+                        Log.e(LOG_TAG, "Error writing printed content", ioe);
+                        writeResultCallback.onWriteFailed(null);
+                    }
+                } finally {
+                    if (pdfDocument != null) {
+                        pdfDocument.close();
+                    }
+                    if (fileDescriptor != null) {
+                        try {
+                            fileDescriptor.close();
+                        } catch (IOException ioe) {
+                            /* ignore */
+                        }
+                    }
+                }
+            }
+        };
+
+        PrintManager printManager = (PrintManager) mContext.getSystemService(Context.PRINT_SERVICE);
+        PrintAttributes.Builder builder = new PrintAttributes.Builder();
+        builder.setColorMode(mColorMode);
+
+        if (mOrientation == ORIENTATION_LANDSCAPE) {
+            builder.setMediaSize(PrintAttributes.MediaSize.UNKNOWN_LANDSCAPE);
+        } else if (mOrientation == ORIENTATION_PORTRAIT) {
+            builder.setMediaSize(PrintAttributes.MediaSize.UNKNOWN_PORTRAIT);
+        }
+        PrintAttributes attr = builder.build();
+
+        printManager.print(jobName, printDocumentAdapter, attr);
     }
 
     /**
@@ -274,10 +483,20 @@
         if (sampleSize <= 0 || 0 >= (int) (Math.min(w, h) / sampleSize)) {
             return null;
         }
-        BitmapFactory.Options options = new BitmapFactory.Options();
-        options.inMutable = true;
-        options.inSampleSize = sampleSize;
-        return loadBitmap(uri, options);
+        BitmapFactory.Options decodeOptions = null;
+        synchronized (mLock) { // prevent race with set null below
+            mDecodeOptions = new BitmapFactory.Options();
+            mDecodeOptions.inMutable = true;
+            mDecodeOptions.inSampleSize = sampleSize;
+            decodeOptions = mDecodeOptions;
+        }
+        try {
+            return loadBitmap(uri, decodeOptions);
+        } finally {
+            synchronized (mLock) {
+                mDecodeOptions = null;
+            }
+        }
     }
 
     /**