/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.support.v4.print; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; import android.graphics.Matrix; import android.graphics.Paint; 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; import android.print.PageRange; import android.print.PrintAttributes; import android.print.PrintDocumentAdapter; import android.print.PrintDocumentInfo; import android.print.PrintManager; import android.print.PrintAttributes.MediaSize; import android.print.pdf.PrintedPdfDocument; import android.util.Log; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; /** * Kitkat specific PrintManager API implementation. */ 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 */ public static final int SCALE_MODE_FIT = 1; /** * image will fill the paper and be cropped (default) */ 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) */ public static final int COLOR_MODE_COLOR = 2; public interface OnPrintFinishCallback { public void onFinish(); } /** * Whether the PrintActivity respects the suggested orientation */ protected boolean mPrintActivityRespectsOrientation; /** * Whether the print subsystem handles min margins correctly. If not the print helper needs to * fake this. */ protected boolean mIsMinMarginsHandlingCorrect; int mScaleMode = SCALE_MODE_FILL; int mColorMode = COLOR_MODE_COLOR; int mOrientation; PrintHelperKitkat(Context context) { mPrintActivityRespectsOrientation = true; mIsMinMarginsHandlingCorrect = true; mContext = context; } /** * Selects whether the image will fill the paper and be cropped *
* {@link #SCALE_MODE_FIT} * or whether the image will be scaled but leave white space * {@link #SCALE_MODE_FILL}. * * @param scaleMode {@link #SCALE_MODE_FIT} or * {@link #SCALE_MODE_FILL} */ public void setScaleMode(int scaleMode) { mScaleMode = scaleMode; } /** * Returns the scale mode with which the image will fill the paper. * * @return The scale Mode: {@link #SCALE_MODE_FIT} or * {@link #SCALE_MODE_FILL} */ public int getScaleMode() { return mScaleMode; } /** * Sets whether the image will be printed in color (default) * {@link #COLOR_MODE_COLOR} or in back and white * {@link #COLOR_MODE_MONOCHROME}. * * @param colorMode The color mode which is one of * {@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() { /// Unset defaults to landscape but might turn image if (mOrientation == 0) { return ORIENTATION_LANDSCAPE; } 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} * and {@link #COLOR_MODE_MONOCHROME}. */ public int getColorMode() { return mColorMode; } /** * Check if the supplied bitmap should best be printed on a portrait orientation paper. * * @param bitmap The bitmap to be printed. * @return true iff the picture should best be printed on a portrait orientation paper. */ private static boolean isPortrait(Bitmap bitmap) { if (bitmap.getWidth() <= bitmap.getHeight()) { return true; } else { return false; } } /** * Create a build with a copy from the other print attributes. * * @param other The other print attributes * * @return A builder that will build print attributes that match the other attributes */ protected PrintAttributes.Builder copyAttributes(PrintAttributes other) { PrintAttributes.Builder b = (new PrintAttributes.Builder()) .setMediaSize(other.getMediaSize()) .setResolution(other.getResolution()) .setMinMargins(other.getMinMargins()); if (other.getColorMode() != 0) { b.setColorMode(other.getColorMode()); } return b; } /** * Prints a bitmap. * * @param jobName The print job name. * @param bitmap The bitmap to print. * @param callback Optional callback to observe when printing is finished. */ public void printBitmap(final String jobName, final Bitmap bitmap, final OnPrintFinishCallback callback) { if (bitmap == null) { return; } final int fittingMode = mScaleMode; // grab the fitting mode at time of call PrintManager printManager = (PrintManager) mContext.getSystemService(Context.PRINT_SERVICE); PrintAttributes.MediaSize mediaSize; if (isPortrait(bitmap)) { mediaSize = PrintAttributes.MediaSize.UNKNOWN_PORTRAIT; } else { mediaSize = PrintAttributes.MediaSize.UNKNOWN_LANDSCAPE; } PrintAttributes attr = new PrintAttributes.Builder() .setMediaSize(mediaSize) .setColorMode(mColorMode) .build(); printManager.print(jobName, new PrintDocumentAdapter() { private PrintAttributes mAttributes; @Override public void onLayout(PrintAttributes oldPrintAttributes, PrintAttributes newPrintAttributes, CancellationSignal cancellationSignal, LayoutResultCallback layoutResultCallback, Bundle bundle) { mAttributes = newPrintAttributes; PrintDocumentInfo info = new PrintDocumentInfo.Builder(jobName) .setContentType(PrintDocumentInfo.CONTENT_TYPE_PHOTO) .setPageCount(1) .build(); boolean changed = !newPrintAttributes.equals(oldPrintAttributes); layoutResultCallback.onLayoutFinished(info, changed); } @Override public void onWrite(PageRange[] pageRanges, ParcelFileDescriptor fileDescriptor, CancellationSignal cancellationSignal, WriteResultCallback writeResultCallback) { writeBitmap(mAttributes, fittingMode, bitmap, fileDescriptor, writeResultCallback); } @Override public void onFinish() { if (callback != null) { callback.onFinish(); } } }, attr); } /** * 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; } /** * Write a bitmap for a PDF document. * * @param attributes The print attributes * @param fittingMode How to fit the bitmap * @param bitmap The bitmap to write * @param fileDescriptor The file to write to * @param writeResultCallback Callback to call once written */ private void writeBitmap(PrintAttributes attributes, int fittingMode, Bitmap bitmap, ParcelFileDescriptor fileDescriptor, PrintDocumentAdapter.WriteResultCallback writeResultCallback) { PrintAttributes pdfAttributes; if (mIsMinMarginsHandlingCorrect) { pdfAttributes = attributes; } else { // If the handling of any margin != 0 is broken, strip the margins and add them to the // bitmap later pdfAttributes = copyAttributes(attributes) .setMinMargins(new PrintAttributes.Margins(0,0,0,0)).build(); } PrintedPdfDocument pdfDocument = new PrintedPdfDocument(mContext, pdfAttributes); Bitmap maybeGrayscale = convertBitmapForColorMode(bitmap, pdfAttributes.getColorMode()); try { Page page = pdfDocument.startPage(1); RectF contentRect; if (mIsMinMarginsHandlingCorrect) { contentRect = new RectF(page.getInfo().getContentRect()); } else { // Create dummy doc that has the margins to compute correctly sized content // rectangle PrintedPdfDocument dummyDocument = new PrintedPdfDocument(mContext, attributes); Page dummyPage = dummyDocument.startPage(1); contentRect = new RectF(dummyPage.getInfo().getContentRect()); dummyDocument.finishPage(dummyPage); dummyDocument.close(); } // Resize bitmap Matrix matrix = getMatrix( maybeGrayscale.getWidth(), maybeGrayscale.getHeight(), contentRect, fittingMode); if (mIsMinMarginsHandlingCorrect) { // The pdfDocument takes care of the positioning and margins } else { // Move it to the correct position. matrix.postTranslate(contentRect.left, contentRect.top); // Cut off margins page.getCanvas().clipRect(contentRect); } // Draw the bitmap. page.getCanvas().drawBitmap(maybeGrayscale, 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 { pdfDocument.close(); if (fileDescriptor != null) { try { fileDescriptor.close(); } catch (IOException ioe) { // ignore } } // If we created a new instance for grayscaling, then recycle it here. if (maybeGrayscale != bitmap) { maybeGrayscale.recycle(); } } } /** * Prints an image located at the Uri. Image types supported are those of *BitmapFactory.decodeStream
(JPEG, GIF, PNG, BMP, WEBP)
*
* @param jobName The print job name.
* @param imageFile The Uri
pointing to an image to print.
* @param callback Optional callback to observe when printing is finished.
* @throws FileNotFoundException if Uri
is not pointing to a valid image.
*/
public void printBitmap(final String jobName, final Uri imageFile,
final OnPrintFinishCallback callback) throws FileNotFoundException {
final int fittingMode = mScaleMode;
PrintDocumentAdapter printDocumentAdapter = new PrintDocumentAdapter() {
private PrintAttributes mAttributes;
AsyncTask