| /* | |
| * Copyright (C) 2016 The Android Open Source Project | |
| * Copyright (C) 2016 Mopria Alliance, Inc. | |
| * | |
| * 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.ipp; | |
| import android.content.Context; | |
| import android.graphics.pdf.PdfRenderer; | |
| import android.net.Uri; | |
| import android.os.AsyncTask; | |
| import android.os.Build; | |
| import android.os.ParcelFileDescriptor; | |
| import android.print.PrintAttributes; | |
| import android.print.PrintDocumentInfo; | |
| import android.print.PrintJobInfo; | |
| import android.printservice.PrintJob; | |
| import android.util.Log; | |
| import android.view.Gravity; | |
| import com.android.bips.ImagePrintActivity; | |
| import com.android.bips.jni.BackendConstants; | |
| import com.android.bips.jni.LocalJobParams; | |
| import com.android.bips.jni.LocalPrinterCapabilities; | |
| import com.android.bips.jni.MediaSizes; | |
| import com.android.bips.util.FileUtils; | |
| import java.io.BufferedOutputStream; | |
| import java.io.File; | |
| import java.io.FileOutputStream; | |
| import java.io.IOException; | |
| /** | |
| * A background task that starts sending a print job. The result of this task is an integer | |
| * defined by {@link Backend} ERROR_* codes or a non-negative code for success. | |
| */ | |
| class StartJobTask extends AsyncTask<Void, Void, Integer> { | |
| private static final String TAG = StartJobTask.class.getSimpleName(); | |
| private static final boolean DEBUG = false; | |
| private static final String MIME_TYPE_PDF = "application/pdf"; | |
| // see wprint_df_types.h for enum values | |
| private static final int MEDIA_TYPE_PLAIN = 0; | |
| private static final int MEDIA_TYPE_AUTO = 98; | |
| // Unused but present | |
| // private static final int MEDIA_TYPE_PHOTO = 1; | |
| // private static final int MEDIA_TYPE_PHOTO_GLOSSY = 2; | |
| private static final int SIDES_SIMPLEX = 0; | |
| private static final int SIDES_DUPLEX_LONG_EDGE = 1; | |
| private static final int SIDES_DUPLEX_SHORT_EDGE = 2; | |
| private static final int RESOLUTION_300_DPI = 300; | |
| private static final int COLOR_SPACE_MONOCHROME = 0; | |
| private static final int COLOR_SPACE_COLOR = 1; | |
| private static final int BORDERLESS_OFF = 0; | |
| private static final int BORDERLESS_ON = 1; | |
| private final Context mContext; | |
| private final Backend mBackend; | |
| private final Uri mDestination; | |
| private final LocalPrinterCapabilities mCapabilities; | |
| private final LocalJobParams mJobParams; | |
| private final ParcelFileDescriptor mSourceFileDescriptor; | |
| private final String mJobId; | |
| private final PrintJobInfo mJobInfo; | |
| private final PrintDocumentInfo mDocInfo; | |
| private final MediaSizes mMediaSizes; | |
| StartJobTask(Context context, Backend backend, Uri destination, PrintJob printJob, | |
| LocalPrinterCapabilities capabilities) { | |
| mContext = context; | |
| mBackend = backend; | |
| mDestination = destination; | |
| mCapabilities = capabilities; | |
| mJobParams = new LocalJobParams(); | |
| mJobId = printJob.getId().toString(); | |
| mJobInfo = printJob.getInfo(); | |
| mDocInfo = printJob.getDocument().getInfo(); | |
| mSourceFileDescriptor = printJob.getDocument().getData(); | |
| mMediaSizes = MediaSizes.getInstance(mContext); | |
| } | |
| private void populateJobParams() { | |
| PrintAttributes.MediaSize mediaSize = mJobInfo.getAttributes().getMediaSize(); | |
| mJobParams.borderless = isBorderless() ? BORDERLESS_ON : BORDERLESS_OFF; | |
| mJobParams.duplex = getSides(); | |
| mJobParams.num_copies = mJobInfo.getCopies(); | |
| mJobParams.pdf_render_resolution = RESOLUTION_300_DPI; | |
| mJobParams.fit_to_page = !getFillPage(); | |
| mJobParams.fill_page = getFillPage(); | |
| mJobParams.job_name = mJobInfo.getLabel(); | |
| mJobParams.job_originating_user_name = Build.MODEL; | |
| mJobParams.auto_rotate = false; | |
| mJobParams.portrait_mode = mediaSize == null || mediaSize.isPortrait(); | |
| mJobParams.landscape_mode = !mJobParams.portrait_mode; | |
| mJobParams.media_size = mMediaSizes.toMediaCode(mediaSize); | |
| mJobParams.media_type = getMediaType(); | |
| mJobParams.color_space = getColorSpace(); | |
| mJobParams.document_category = getDocumentCategory(); | |
| mJobParams.shared_photo = isSharedPhoto(); | |
| mJobParams.preserve_scaling = false; | |
| mJobParams.job_margin_top = Math.max(mJobParams.job_margin_top, 0.0f); | |
| mJobParams.job_margin_left = Math.max(mJobParams.job_margin_left, 0.0f); | |
| mJobParams.job_margin_right = Math.max(mJobParams.job_margin_right, 0.0f); | |
| mJobParams.job_margin_bottom = Math.max(mJobParams.job_margin_bottom, 0.0f); | |
| mJobParams.alignment = Gravity.CENTER; | |
| } | |
| @Override | |
| protected Integer doInBackground(Void... voids) { | |
| if (DEBUG) Log.d(TAG, "doInBackground() job=" + mJobParams + ", cap=" + mCapabilities); | |
| File tempFolder = new File(mContext.getFilesDir(), Backend.TEMP_JOB_FOLDER); | |
| if (!FileUtils.makeDirectory(tempFolder)) { | |
| Log.w(TAG, "makeDirectory failure"); | |
| return Backend.ERROR_FILE; | |
| } | |
| File pdfFile = new File(tempFolder, mJobId + ".pdf"); | |
| try { | |
| try { | |
| FileUtils.copy(new ParcelFileDescriptor.AutoCloseInputStream(mSourceFileDescriptor), | |
| new BufferedOutputStream(new FileOutputStream(pdfFile))); | |
| } catch (IOException e) { | |
| Log.w(TAG, "Error while copying to " + pdfFile, e); | |
| return Backend.ERROR_FILE; | |
| } | |
| String[] files = new String[]{pdfFile.toString()}; | |
| // Address, without port. | |
| String address = mDestination.getHost() + mDestination.getPath(); | |
| if (isCancelled()) { | |
| return Backend.ERROR_CANCEL; | |
| } | |
| // Get default job parameters | |
| int result = mBackend.nativeGetDefaultJobParameters(mJobParams); | |
| if (result != 0) { | |
| if (DEBUG) Log.w(TAG, "nativeGetDefaultJobParameters failure: " + result); | |
| return Backend.ERROR_UNKNOWN; | |
| } | |
| if (isCancelled()) { | |
| return Backend.ERROR_CANCEL; | |
| } | |
| // Fill in job parameters from capabilities and print job info. | |
| populateJobParams(); | |
| try (PdfRenderer renderer = new PdfRenderer( | |
| ParcelFileDescriptor.open(pdfFile, ParcelFileDescriptor.MODE_READ_ONLY)); | |
| PdfRenderer.Page page = renderer.openPage(0)) { | |
| if (mJobParams.portrait_mode) { | |
| mJobParams.source_height = (float) page.getHeight() / 72; | |
| mJobParams.source_width = (float) page.getWidth() / 72; | |
| } else { | |
| mJobParams.source_width = (float) page.getHeight() / 72; | |
| mJobParams.source_height = (float) page.getWidth() / 72; | |
| } | |
| } catch (IOException e) { | |
| Log.w(TAG, "Error while getting source width, height", e); | |
| } | |
| // Finalize job parameters | |
| mBackend.nativeGetFinalJobParameters(mJobParams, mCapabilities); | |
| if (isCancelled()) { | |
| return Backend.ERROR_CANCEL; | |
| } | |
| if (DEBUG) { | |
| Log.d(TAG, "nativeStartJob address=" + address | |
| + " port=" + mDestination.getPort() + " mime=" + MIME_TYPE_PDF | |
| + " files=" + files[0] + " job=" + mJobParams); | |
| } | |
| // Initiate job | |
| result = mBackend.nativeStartJob(Backend.getIp(address), mDestination.getPort(), | |
| MIME_TYPE_PDF, mJobParams, mCapabilities, files, null, | |
| mDestination.getScheme()); | |
| if (result < 0) { | |
| Log.w(TAG, "nativeStartJob failure: " + result); | |
| return Backend.ERROR_UNKNOWN; | |
| } | |
| pdfFile = null; | |
| return result; | |
| } finally { | |
| if (pdfFile != null) { | |
| pdfFile.delete(); | |
| } | |
| } | |
| } | |
| private boolean isBorderless() { | |
| return mCapabilities.borderless | |
| && mDocInfo.getContentType() == PrintDocumentInfo.CONTENT_TYPE_PHOTO; | |
| } | |
| private int getSides() { | |
| // Never duplex photo media; may damage printers | |
| if (mDocInfo.getContentType() == PrintDocumentInfo.CONTENT_TYPE_PHOTO) { | |
| return SIDES_SIMPLEX; | |
| } | |
| switch (mJobInfo.getAttributes().getDuplexMode()) { | |
| case PrintAttributes.DUPLEX_MODE_LONG_EDGE: | |
| return SIDES_DUPLEX_LONG_EDGE; | |
| case PrintAttributes.DUPLEX_MODE_SHORT_EDGE: | |
| return SIDES_DUPLEX_SHORT_EDGE; | |
| case PrintAttributes.DUPLEX_MODE_NONE: | |
| default: | |
| return SIDES_SIMPLEX; | |
| } | |
| } | |
| private boolean getFillPage() { | |
| switch (mDocInfo.getContentType()) { | |
| case PrintDocumentInfo.CONTENT_TYPE_PHOTO: | |
| return true; | |
| case PrintDocumentInfo.CONTENT_TYPE_UNKNOWN: | |
| case PrintDocumentInfo.CONTENT_TYPE_DOCUMENT: | |
| default: | |
| return false; | |
| } | |
| } | |
| private int getMediaType() { | |
| int mediaType = MEDIA_TYPE_PLAIN; | |
| for (int supportedType : mCapabilities.supportedMediaTypes) { | |
| if (supportedType == MEDIA_TYPE_AUTO) { | |
| // if auto media is supported, use that and break out of the loop | |
| mediaType = MEDIA_TYPE_AUTO; | |
| break; | |
| } else if (mDocInfo.getContentType() == PrintDocumentInfo.CONTENT_TYPE_PHOTO | |
| && supportedType > mediaType) { | |
| // Select the best (highest #) supported type for photos | |
| mediaType = supportedType; | |
| } | |
| } | |
| return mediaType; | |
| } | |
| private int getColorSpace() { | |
| switch (mJobInfo.getAttributes().getColorMode()) { | |
| case PrintAttributes.COLOR_MODE_COLOR: | |
| return COLOR_SPACE_COLOR; | |
| case PrintAttributes.COLOR_MODE_MONOCHROME: | |
| default: | |
| return COLOR_SPACE_MONOCHROME; | |
| } | |
| } | |
| private String getDocumentCategory() { | |
| switch (mDocInfo.getContentType()) { | |
| case PrintDocumentInfo.CONTENT_TYPE_PHOTO: | |
| return BackendConstants.PRINT_DOCUMENT_CATEGORY__PHOTO; | |
| case PrintDocumentInfo.CONTENT_TYPE_DOCUMENT: | |
| default: | |
| return BackendConstants.PRINT_DOCUMENT_CATEGORY__DOCUMENT; | |
| } | |
| } | |
| private boolean isSharedPhoto() { | |
| return mJobInfo.getId().equals(ImagePrintActivity.getLastPrintJobId()); | |
| } | |
| } |