support share-to-print of images and PDFs
Make it easier for apps to print to any supported printer via a normal
share intent. Provides appropriate content resolution to PrintManager
for printing to any installed and enabled Print Service. Image content
is downscaled to a good DPI for preview (screen DPI) or delivery (300
DPI). Fit vs Fill options are activated by the user's landscape vs
portrait print attribute selection. Finally, Photos default to a
locale-specific default photo media size.
Test: Share any image or PDF then select "Print"
Change-Id: Iccaf987b2754bdb7a723ad5e94d414c1f33f5ef2
Signed-off-by: Glade Diviney <mopriadevteam@gmail.com>
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 23f290b..47c3077 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -67,5 +67,29 @@
android:exported="true"
android:configChanges="orientation|keyboardHidden|screenSize"
android:permission="android.permission.START_PRINT_SERVICE_CONFIG_ACTIVITY" />
+
+ <activity
+ android:name="com.android.bips.ImagePrintActivity"
+ android:label="@string/print"
+ android:theme="@android:style/Theme.Translucent.NoTitleBar"
+ android:configChanges="keyboardHidden|orientation|screenSize">
+ <intent-filter>
+ <action android:name="android.intent.action.SEND" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:mimeType="image/*" />
+ </intent-filter>
+ </activity>
+
+ <activity
+ android:name="com.android.bips.PdfPrintActivity"
+ android:label="@string/print"
+ android:theme="@android:style/Theme.Translucent.NoTitleBar"
+ android:configChanges="keyboardHidden|orientation|screenSize">
+ <intent-filter>
+ <action android:name="android.intent.action.SEND" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:mimeType="application/pdf" />
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 0a93da9..46b5ce4 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -115,4 +115,7 @@
<string name="wifi_direct_permission_rationale">Default Print Service needs location permission to find nearby devices.</string>
<!-- Button label in a notification or dialog. This button leads to a request to grant permissions [CHAR LIMIT=20] -->
<string name="fix">Review permission</string>
+
+ <!-- Share-to-print label [CHAR LIMIT=20] -->
+ <string name="print">Print</string>
</resources>
diff --git a/src/com/android/bips/ImagePrintActivity.java b/src/com/android/bips/ImagePrintActivity.java
new file mode 100644
index 0000000..ee9834e
--- /dev/null
+++ b/src/com/android/bips/ImagePrintActivity.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2020 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 com.android.bips;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Paint;
+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.PrintJob;
+import android.print.PrintManager;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.webkit.URLUtil;
+import android.widget.Toast;
+
+import com.android.bips.jni.MediaSizes;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+
+/**
+ * Activity to receive share-to-print intents for images.
+ */
+public class ImagePrintActivity extends Activity {
+ private static final String TAG = ImagePrintActivity.class.getSimpleName();
+ private static final boolean DEBUG = false;
+ private static final int PRINT_DPI = 300;
+ private static final PrintAttributes.MediaSize DEFAULT_PHOTO_MEDIA =
+ PrintAttributes.MediaSize.NA_INDEX_4X6;
+
+ /** Countries where A5 is a more common photo media size. */
+ private static final String[] ISO_A5_COUNTRY_CODES = {
+ "IQ", "SY", "YE", "VN", "MA"
+ };
+
+ private CancellationSignal mCancellationSignal = new CancellationSignal();
+ private String mJobName;
+ private Bitmap mBitmap;
+ private DisplayMetrics mDisplayMetrics = new DisplayMetrics();
+ private Runnable mOnBitmapLoaded = null;
+ private AsyncTask<?, ?, ?> mTask = null;
+ private PrintJob mPrintJob;
+ private Bitmap mGrayscaleBitmap;
+ private PrintAttributes.MediaSize mDefaultMediaSize = null;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ String action = getIntent().getAction();
+ Uri contentUri = null;
+ if (Intent.ACTION_SEND.equals(action)) {
+ contentUri = getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
+ } else if (Intent.ACTION_VIEW.equals(action)) {
+ contentUri = getIntent().getData();
+ }
+ if (contentUri == null) {
+ finish();
+ }
+ getWindowManager().getDefaultDisplay().getMetrics(mDisplayMetrics);
+ mJobName = URLUtil.guessFileName(getIntent().getStringExtra(Intent.EXTRA_TEXT), null,
+ getIntent().resolveType(this));
+
+ if (DEBUG) Log.d(TAG, "onCreate() uri=" + contentUri + " jobName=" + mJobName);
+
+ // Load the bitmap while we start the print
+ mTask = new LoadBitmapTask().execute(contentUri);
+ }
+
+ /**
+ * A background task to load the bitmap and start the print job.
+ */
+ private class LoadBitmapTask extends AsyncTask<Uri, Boolean, Bitmap> {
+ @Override
+ protected Bitmap doInBackground(Uri... uris) {
+ if (DEBUG) Log.d(TAG, "Loading bitmap from stream");
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ loadBitmap(uris[0], options);
+ if (options.outWidth <= 0 || options.outHeight <= 0) {
+ Log.w(TAG, "Failed to load bitmap");
+ return null;
+ }
+ if (mCancellationSignal.isCanceled()) {
+ return null;
+ } else {
+ // Publish progress and load for real
+ publishProgress(options.outHeight > options.outWidth);
+ options.inJustDecodeBounds = false;
+ return loadBitmap(uris[0], options);
+ }
+ }
+
+ /**
+ * Return a bitmap as loaded from {@param contentUri} using {@param options}.
+ */
+ private Bitmap loadBitmap(Uri contentUri, BitmapFactory.Options options) {
+ try (InputStream inputStream = getContentResolver().openInputStream(contentUri)) {
+ return BitmapFactory.decodeStream(inputStream, null, options);
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to load bitmap", e);
+ return null;
+ }
+ }
+
+ @Override
+ protected void onProgressUpdate(Boolean... values) {
+ // Once we have a portrait/landscape determination, launch the print job
+ boolean isPortrait = values[0];
+ if (DEBUG) Log.d(TAG, "startPrint(portrait=" + isPortrait + ")");
+ PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
+ if (printManager == null) {
+ finish();
+ return;
+ }
+
+ PrintAttributes printAttributes = new PrintAttributes.Builder()
+ .setMediaSize(isPortrait ? getLocaleDefaultMediaSize() :
+ getLocaleDefaultMediaSize().asLandscape())
+ .setColorMode(PrintAttributes.COLOR_MODE_COLOR)
+ .build();
+ mPrintJob = printManager.print(mJobName, new ImageAdapter(), printAttributes);
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ if (mCancellationSignal.isCanceled()) {
+ if (DEBUG) Log.d(TAG, "LoadBitmapTask cancelled");
+ } else if (bitmap == null) {
+ if (mPrintJob != null) {
+ mPrintJob.cancel();
+ }
+ Toast.makeText(ImagePrintActivity.this, R.string.unreadable_input,
+ Toast.LENGTH_LONG).show();
+ finish();
+ } else {
+ if (DEBUG) Log.d(TAG, "LoadBitmapTask complete");
+ mBitmap = bitmap;
+ if (mOnBitmapLoaded != null) {
+ mOnBitmapLoaded.run();
+ }
+ }
+ }
+ }
+
+ private PrintAttributes.MediaSize getLocaleDefaultMediaSize() {
+ if (mDefaultMediaSize == null) {
+ String country = getResources().getConfiguration().getLocales().get(0).getCountry();
+ Set<String> a5Countries = new HashSet<>(Arrays.asList(ISO_A5_COUNTRY_CODES));
+ if (Locale.JAPAN.getCountry().equals(country)) {
+ // Photo L is a more common media size in Japan
+ mDefaultMediaSize = new PrintAttributes.MediaSize(MediaSizes.OE_PHOTO_L,
+ getString(R.string.media_size_l), 3500, 5000);
+ } else if (a5Countries.contains(country)) {
+ mDefaultMediaSize = PrintAttributes.MediaSize.ISO_A5;
+ } else {
+ mDefaultMediaSize = DEFAULT_PHOTO_MEDIA;
+ }
+ }
+ return mDefaultMediaSize;
+ }
+
+ @Override
+ protected void onDestroy() {
+ if (DEBUG) Log.d(TAG, "onDestroy()");
+ mCancellationSignal.cancel();
+ if (mTask != null) {
+ mTask.cancel(true);
+ mTask = null;
+ }
+ if (mBitmap != null) {
+ mBitmap.recycle();
+ mBitmap = null;
+ }
+ if (mGrayscaleBitmap != null) {
+ mGrayscaleBitmap.recycle();
+ mGrayscaleBitmap = null;
+ }
+ super.onDestroy();
+ }
+
+ /**
+ * An adapter that converts the image to PDF format as requested by the print system
+ */
+ private class ImageAdapter extends PrintDocumentAdapter {
+ private PrintAttributes mAttributes;
+ private int mDpi;
+
+ @Override
+ public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
+ CancellationSignal cancellationSignal, LayoutResultCallback callback,
+ Bundle bundle) {
+ if (DEBUG) Log.d(TAG, "onLayout() attrs=" + newAttributes);
+
+ if (mBitmap == null) {
+ if (DEBUG) Log.d(TAG, "waiting for bitmap...");
+ // Try again when bitmap has arrived
+ mOnBitmapLoaded = () -> onLayout(oldAttributes, newAttributes, cancellationSignal,
+ callback, bundle);
+ return;
+ }
+
+ int oldDpi = mDpi;
+ mAttributes = newAttributes;
+
+ // Calculate required DPI (print or display)
+ if (bundle.getBoolean(EXTRA_PRINT_PREVIEW, false)) {
+ PrintAttributes.MediaSize mediaSize = mAttributes.getMediaSize();
+ mDpi = Math.min(
+ mDisplayMetrics.widthPixels * 1000 / mediaSize.getWidthMils(),
+ mDisplayMetrics.heightPixels * 1000 / mediaSize.getHeightMils());
+ } else {
+ mDpi = PRINT_DPI;
+ }
+
+ PrintDocumentInfo info = new PrintDocumentInfo.Builder(mJobName)
+ .setContentType(PrintDocumentInfo.CONTENT_TYPE_PHOTO)
+ .setPageCount(1)
+ .build();
+ callback.onLayoutFinished(info, !newAttributes.equals(oldAttributes) || oldDpi != mDpi);
+ }
+
+ @Override
+ public void onWrite(PageRange[] pageRanges, ParcelFileDescriptor fileDescriptor,
+ CancellationSignal cancellationSignal, WriteResultCallback callback) {
+ if (DEBUG) Log.d(TAG, "onWrite()");
+ mCancellationSignal = cancellationSignal;
+
+ mTask = new ImageToPdfTask(ImagePrintActivity.this, getBitmap(mAttributes), mAttributes,
+ mDpi, cancellationSignal) {
+ @Override
+ protected void onPostExecute(Throwable throwable) {
+ if (cancellationSignal.isCanceled()) {
+ if (DEBUG) Log.d(TAG, "writeBitmap() cancelled");
+ callback.onWriteCancelled();
+ } else if (throwable != null) {
+ Log.w(TAG, "Failed to write bitmap", throwable);
+ callback.onWriteFailed(null);
+ } else {
+ if (DEBUG) Log.d(TAG, "Calling onWriteFinished");
+ callback.onWriteFinished(new PageRange[] { PageRange.ALL_PAGES });
+ }
+ mTask = null;
+ }
+ }.execute(fileDescriptor);
+ }
+
+ @Override
+ public void onFinish() {
+ if (DEBUG) Log.d(TAG, "onFinish()");
+ finish();
+ }
+ }
+
+ /**
+ * Return an appropriate bitmap to use when rendering {@param attributes}.
+ */
+ private Bitmap getBitmap(PrintAttributes attributes) {
+ if (attributes.getColorMode() == PrintAttributes.COLOR_MODE_MONOCHROME) {
+ if (mGrayscaleBitmap == null) {
+ mGrayscaleBitmap = Bitmap.createBitmap(mBitmap.getWidth(), mBitmap.getHeight(),
+ Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(mGrayscaleBitmap);
+ Paint paint = new Paint();
+ ColorMatrix colorMatrix = new ColorMatrix();
+ colorMatrix.setSaturation(0);
+ paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
+ canvas.drawBitmap(mBitmap, 0, 0, paint);
+ }
+ return mGrayscaleBitmap;
+ } else {
+ return mBitmap;
+ }
+ }
+}
diff --git a/src/com/android/bips/ImageToPdfTask.java b/src/com/android/bips/ImageToPdfTask.java
new file mode 100644
index 0000000..65e523c
--- /dev/null
+++ b/src/com/android/bips/ImageToPdfTask.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2020 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 com.android.bips;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.graphics.pdf.PdfDocument;
+import android.os.AsyncTask;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.print.PrintAttributes;
+import android.print.pdf.PrintedPdfDocument;
+import android.util.Log;
+
+import java.io.FileOutputStream;
+
+/**
+ * A background task that optimizes a {@link Bitmap}, renders it into a PDF, and delivers the PDF
+ * to a {@link ParcelFileDescriptor}.
+ */
+class ImageToPdfTask extends AsyncTask<ParcelFileDescriptor, Void, Throwable> {
+ private static final String TAG = ImageToPdfTask.class.getSimpleName();
+ private static final boolean DEBUG = false;
+ private static final float POINTS_PER_INCH = 72;
+
+ private final PrintedPdfDocument mDocument;
+ private final Bitmap mBitmap;
+ private final PrintAttributes mAttributes;
+ private final int mDpi;
+ private final CancellationSignal mCancellationSignal;
+
+ ImageToPdfTask(Context context, Bitmap bitmap, PrintAttributes attributes, int dpi,
+ CancellationSignal cancellationSignal) {
+ mBitmap = bitmap;
+ mAttributes = attributes;
+ mCancellationSignal = cancellationSignal;
+ mDpi = dpi;
+ mDocument = new PrintedPdfDocument(context, mAttributes);
+ }
+
+ @Override
+ protected Throwable doInBackground(ParcelFileDescriptor... outputs) {
+ try (ParcelFileDescriptor output = outputs[0]) {
+ if (DEBUG) Log.d(TAG, "creating document at dpi=" + mDpi);
+ writeBitmapToDocument();
+ mCancellationSignal.throwIfCanceled();
+ if (DEBUG) Log.d(TAG, "writing to output stream");
+ mDocument.writeTo(new FileOutputStream(output.getFileDescriptor()));
+ mDocument.close();
+ if (DEBUG) Log.d(TAG, "finished sending");
+ return null;
+ } catch (Throwable t) {
+ return t;
+ }
+ }
+
+ /** Create a one-page PDF document containing the bitmap */
+ private void writeBitmapToDocument() {
+ PdfDocument.Page page = mDocument.startPage(1);
+ if (mAttributes.getMediaSize().isPortrait() == mBitmap.getWidth() < mBitmap.getHeight()) {
+ writeBitmapToPage(page, true);
+ } else {
+ // If user selects the opposite orientation, fit instead of fill.
+ writeBitmapToPage(page, false);
+ }
+ mDocument.finishPage(page);
+ }
+
+ private void writeBitmapToPage(PdfDocument.Page page, boolean fill) {
+ RectF extent = new RectF(page.getInfo().getContentRect());
+ float scale;
+ boolean rotate;
+ if (fill) {
+ // Fill the entire page with image data
+ scale = Math.max(extent.height() / POINTS_PER_INCH * mDpi / mBitmap.getHeight(),
+ extent.width() / POINTS_PER_INCH * mDpi / mBitmap.getWidth());
+ rotate = false;
+ } else {
+ // Scale and rotate the image to fit entirely on the page
+ scale = Math.min(extent.height() / POINTS_PER_INCH * mDpi / mBitmap.getWidth(),
+ extent.width() / POINTS_PER_INCH * mDpi / mBitmap.getHeight());
+ rotate = true;
+ }
+
+ if (scale >= 1) {
+ // Image will need to be scaled up
+ drawDirect(page, extent, fill, rotate);
+ } else {
+ // Scale image down to the size needed for printing
+ drawOptimized(page, extent, scale, rotate);
+ }
+ }
+
+ /**
+ * Render the source bitmap directly into the PDF
+ */
+ private void drawDirect(PdfDocument.Page page, RectF extent, boolean fill, boolean rotate) {
+ float scale;
+ if (fill) {
+ scale = Math.max(extent.height() / mBitmap.getHeight(),
+ extent.width() / mBitmap.getWidth());
+ } else {
+ scale = Math.min(extent.height() / mBitmap.getWidth(),
+ extent.width() / mBitmap.getHeight());
+ }
+
+ float offsetX = (extent.width() - mBitmap.getWidth() * scale) / 2;
+ float offsetY = (extent.height() - mBitmap.getHeight() * scale) / 2;
+
+ Matrix matrix = new Matrix();
+ if (rotate) {
+ matrix.postRotate(90, mBitmap.getWidth() / 2, mBitmap.getHeight() / 2);
+ }
+ matrix.postScale(scale, scale);
+ matrix.postTranslate(offsetX, offsetY);
+ page.getCanvas().clipRect(extent);
+ page.getCanvas().drawBitmap(mBitmap, matrix, new Paint(Paint.FILTER_BITMAP_FLAG));
+ }
+
+ /**
+ * Scale down the bitmap to specific DPI to reduce delivered PDF size
+ */
+ private void drawOptimized(PdfDocument.Page page, RectF extent, float scale, boolean rotate) {
+ float targetWidth = (extent.width() / POINTS_PER_INCH * mDpi);
+ float targetHeight = (extent.height() / POINTS_PER_INCH * mDpi);
+ float offsetX = ((targetWidth / scale) - mBitmap.getWidth()) / 2;
+ float offsetY = ((targetHeight / scale) - mBitmap.getHeight()) / 2;
+
+ Bitmap targetBitmap = Bitmap.createBitmap((int) targetWidth, (int) targetHeight,
+ Bitmap.Config.ARGB_8888);
+ Canvas bitmapCanvas = new Canvas(targetBitmap);
+ Matrix matrix = new Matrix();
+ matrix.postScale(scale, scale);
+ if (rotate) {
+ matrix.postRotate(90, targetWidth / 2, targetHeight / 2);
+ }
+ bitmapCanvas.setMatrix(matrix);
+ bitmapCanvas.drawBitmap(mBitmap, offsetX, offsetY, new Paint(Paint.FILTER_BITMAP_FLAG));
+ page.getCanvas().drawBitmap(targetBitmap, null, extent, null);
+ targetBitmap.recycle();
+ }
+}
diff --git a/src/com/android/bips/PdfPrintActivity.java b/src/com/android/bips/PdfPrintActivity.java
new file mode 100644
index 0000000..4463fae
--- /dev/null
+++ b/src/com/android/bips/PdfPrintActivity.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2020 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 com.android.bips;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+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.util.Log;
+import android.webkit.URLUtil;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Activity to receive share-to-print intents for PDF documents.
+ */
+public class PdfPrintActivity extends Activity {
+ private static final String TAG = PdfPrintActivity.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private CancellationSignal mCancellationSignal;
+ private String mJobName;
+ Uri mContentUri = null;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ String action = getIntent().getAction();
+ if (Intent.ACTION_SEND.equals(action)) {
+ mContentUri = getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
+ } else if (Intent.ACTION_VIEW.equals(action)) {
+ mContentUri = getIntent().getData();
+ }
+ if (mContentUri == null) {
+ finish();
+ }
+ mJobName = URLUtil.guessFileName(getIntent().getStringExtra(Intent.EXTRA_TEXT), null,
+ getIntent().resolveType(this));
+ if (DEBUG) Log.d(TAG, "onCreate() uri=" + mContentUri + " jobName=" + mJobName);
+
+ PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
+ if (printManager == null) {
+ finish();
+ return;
+ }
+
+ PrintAttributes printAttributes = new PrintAttributes.Builder()
+ .setColorMode(PrintAttributes.COLOR_MODE_COLOR)
+ .build();
+ printManager.print(mJobName, new PdfAdapter(), printAttributes);
+ }
+
+ @Override
+ protected void onDestroy() {
+ if (DEBUG) Log.d(TAG, "onDestroy()");
+ if (mCancellationSignal != null) {
+ mCancellationSignal.cancel();
+ }
+ super.onDestroy();
+ }
+
+ private class PdfAdapter extends PrintDocumentAdapter {
+ @Override
+ public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
+ CancellationSignal cancellationSignal, LayoutResultCallback callback,
+ Bundle bundle) {
+ if (DEBUG) Log.d(TAG, "onLayout() attrs=" + newAttributes);
+
+ PrintDocumentInfo info = new PrintDocumentInfo.Builder(mJobName)
+ .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
+ .setPageCount(PrintDocumentInfo.PAGE_COUNT_UNKNOWN)
+ .build();
+ callback.onLayoutFinished(info, false);
+ }
+
+ @Override
+ public void onWrite(PageRange[] pageRanges, ParcelFileDescriptor fileDescriptor,
+ CancellationSignal cancellationSignal, WriteResultCallback callback) {
+ if (DEBUG) Log.d(TAG, "onWrite()");
+ mCancellationSignal = cancellationSignal;
+ new PdfDeliverTask(fileDescriptor, callback).execute();
+ }
+
+ @Override
+ public void onFinish() {
+ if (DEBUG) Log.d(TAG, "onFinish()");
+ finish();
+ }
+ }
+
+ private class PdfDeliverTask extends AsyncTask<Void, Void, Void> {
+ ParcelFileDescriptor mDescriptor;
+ PrintDocumentAdapter.WriteResultCallback mCallback;
+
+ PdfDeliverTask(ParcelFileDescriptor descriptor,
+ PrintDocumentAdapter.WriteResultCallback callback) {
+ mDescriptor = descriptor;
+ mCallback = callback;
+ }
+
+ @Override
+ protected Void doInBackground(Void... voids) {
+ try (InputStream in = getContentResolver().openInputStream(mContentUri)) {
+ if (in == null) {
+ throw new IOException("Failed to open input stream");
+ }
+ try (OutputStream out = new FileOutputStream(mDescriptor.getFileDescriptor())) {
+ byte[] buffer = new byte[10 * 1024];
+ int length;
+ while ((length = in.read(buffer)) >= 0 && !mCancellationSignal.isCanceled()) {
+ out.write(buffer, 0, length);
+ }
+ }
+ if (mCancellationSignal.isCanceled()) {
+ mCallback.onWriteCancelled();
+ } else {
+ mCallback.onWriteFinished(new PageRange[] { PageRange.ALL_PAGES });
+ }
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to deliver content", e);
+ mCallback.onWriteFailed(e.getMessage());
+ }
+ return null;
+ }
+ }
+}
diff --git a/src/com/android/bips/discovery/MdnsDiscovery.java b/src/com/android/bips/discovery/MdnsDiscovery.java
index d3fd2f1..51c1781 100644
--- a/src/com/android/bips/discovery/MdnsDiscovery.java
+++ b/src/com/android/bips/discovery/MdnsDiscovery.java
@@ -107,7 +107,7 @@
// Must be IPv4
if (!(info.getHost() instanceof Inet4Address)) {
- if (DEBUG) Log.d(TAG, "Not IPv4" + info);
+ if (DEBUG) Log.d(TAG, "Not IPv4 " + info.getHost());
return null;
}
diff --git a/src/com/android/bips/jni/MediaSizes.java b/src/com/android/bips/jni/MediaSizes.java
index 1ebdf21..4ec76f3 100644
--- a/src/com/android/bips/jni/MediaSizes.java
+++ b/src/com/android/bips/jni/MediaSizes.java
@@ -43,7 +43,7 @@
private static final String OM_CARD = "om_card_54x86mm";
private static final String JIS_B4 = "jis_b4_257x364mm";
private static final String JIS_B5 = "jis_b5_182x257mm";
- private static final String OE_PHOTO_L = "oe_photo-l_3.5x5in";
+ public static final String OE_PHOTO_L = "oe_photo-l_3.5x5in";
private static final String NA_GOVT_LETTER = "na_govt-letter_8x10in";
/** The backend string name for the default media size */