1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * Copyright (C) 2016 Mopria Alliance, Inc. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.android.bips.ipp; 19 20 import android.content.Context; 21 import android.graphics.pdf.PdfRenderer; 22 import android.net.Uri; 23 import android.os.AsyncTask; 24 import android.os.Build; 25 import android.os.ParcelFileDescriptor; 26 import android.print.PrintAttributes; 27 import android.print.PrintDocumentInfo; 28 import android.print.PrintJobInfo; 29 import android.printservice.PrintJob; 30 import android.util.Log; 31 import android.view.Gravity; 32 33 import com.android.bips.ImagePrintActivity; 34 import com.android.bips.jni.BackendConstants; 35 import com.android.bips.jni.LocalJobParams; 36 import com.android.bips.jni.LocalPrinterCapabilities; 37 import com.android.bips.jni.MediaSizes; 38 import com.android.bips.util.FileUtils; 39 40 import java.io.BufferedOutputStream; 41 import java.io.File; 42 import java.io.FileOutputStream; 43 import java.io.IOException; 44 45 /** 46 * A background task that starts sending a print job. The result of this task is an integer 47 * defined by {@link Backend} ERROR_* codes or a non-negative code for success. 48 */ 49 class StartJobTask extends AsyncTask<Void, Void, Integer> { 50 private static final String TAG = StartJobTask.class.getSimpleName(); 51 private static final boolean DEBUG = false; 52 53 private static final String MIME_TYPE_PDF = "application/pdf"; 54 55 // see wprint_df_types.h for enum values 56 private static final int MEDIA_TYPE_PLAIN = 0; 57 private static final int MEDIA_TYPE_AUTO = 98; 58 // Unused but present 59 // private static final int MEDIA_TYPE_PHOTO = 1; 60 // private static final int MEDIA_TYPE_PHOTO_GLOSSY = 2; 61 62 private static final int SIDES_SIMPLEX = 0; 63 private static final int SIDES_DUPLEX_LONG_EDGE = 1; 64 private static final int SIDES_DUPLEX_SHORT_EDGE = 2; 65 66 private static final int RESOLUTION_300_DPI = 300; 67 68 private static final int COLOR_SPACE_MONOCHROME = 0; 69 private static final int COLOR_SPACE_COLOR = 1; 70 71 private static final int BORDERLESS_OFF = 0; 72 private static final int BORDERLESS_ON = 1; 73 74 private final Context mContext; 75 private final Backend mBackend; 76 private final Uri mDestination; 77 private final LocalPrinterCapabilities mCapabilities; 78 private final LocalJobParams mJobParams; 79 private final ParcelFileDescriptor mSourceFileDescriptor; 80 private final String mJobId; 81 private final PrintJobInfo mJobInfo; 82 private final PrintDocumentInfo mDocInfo; 83 private final MediaSizes mMediaSizes; 84 StartJobTask(Context context, Backend backend, Uri destination, PrintJob printJob, LocalPrinterCapabilities capabilities)85 StartJobTask(Context context, Backend backend, Uri destination, PrintJob printJob, 86 LocalPrinterCapabilities capabilities) { 87 mContext = context; 88 mBackend = backend; 89 mDestination = destination; 90 mCapabilities = capabilities; 91 mJobParams = new LocalJobParams(); 92 mJobId = printJob.getId().toString(); 93 mJobInfo = printJob.getInfo(); 94 mDocInfo = printJob.getDocument().getInfo(); 95 mSourceFileDescriptor = printJob.getDocument().getData(); 96 mMediaSizes = MediaSizes.getInstance(mContext); 97 } 98 populateJobParams()99 private void populateJobParams() { 100 PrintAttributes.MediaSize mediaSize = mJobInfo.getAttributes().getMediaSize(); 101 102 mJobParams.borderless = isBorderless() ? BORDERLESS_ON : BORDERLESS_OFF; 103 mJobParams.duplex = getSides(); 104 mJobParams.num_copies = mJobInfo.getCopies(); 105 mJobParams.pdf_render_resolution = RESOLUTION_300_DPI; 106 mJobParams.fit_to_page = !getFillPage(); 107 mJobParams.fill_page = getFillPage(); 108 mJobParams.job_name = mJobInfo.getLabel(); 109 mJobParams.job_originating_user_name = Build.MODEL; 110 mJobParams.auto_rotate = false; 111 mJobParams.portrait_mode = mediaSize == null || mediaSize.isPortrait(); 112 mJobParams.landscape_mode = !mJobParams.portrait_mode; 113 mJobParams.media_size = mMediaSizes.toMediaCode(mediaSize); 114 mJobParams.media_type = getMediaType(); 115 mJobParams.color_space = getColorSpace(); 116 mJobParams.document_category = getDocumentCategory(); 117 mJobParams.shared_photo = isSharedPhoto(); 118 mJobParams.preserve_scaling = false; 119 120 mJobParams.job_margin_top = Math.max(mJobParams.job_margin_top, 0.0f); 121 mJobParams.job_margin_left = Math.max(mJobParams.job_margin_left, 0.0f); 122 mJobParams.job_margin_right = Math.max(mJobParams.job_margin_right, 0.0f); 123 mJobParams.job_margin_bottom = Math.max(mJobParams.job_margin_bottom, 0.0f); 124 125 mJobParams.alignment = Gravity.CENTER; 126 } 127 128 @Override doInBackground(Void... voids)129 protected Integer doInBackground(Void... voids) { 130 if (DEBUG) Log.d(TAG, "doInBackground() job=" + mJobParams + ", cap=" + mCapabilities); 131 File tempFolder = new File(mContext.getFilesDir(), Backend.TEMP_JOB_FOLDER); 132 if (!FileUtils.makeDirectory(tempFolder)) { 133 Log.w(TAG, "makeDirectory failure"); 134 return Backend.ERROR_FILE; 135 } 136 137 File pdfFile = new File(tempFolder, mJobId + ".pdf"); 138 try { 139 try { 140 FileUtils.copy(new ParcelFileDescriptor.AutoCloseInputStream(mSourceFileDescriptor), 141 new BufferedOutputStream(new FileOutputStream(pdfFile))); 142 } catch (IOException e) { 143 Log.w(TAG, "Error while copying to " + pdfFile, e); 144 return Backend.ERROR_FILE; 145 } 146 String[] files = new String[]{pdfFile.toString()}; 147 148 // Address, without port. 149 String address = mDestination.getHost() + mDestination.getPath(); 150 151 if (isCancelled()) { 152 return Backend.ERROR_CANCEL; 153 } 154 155 // Get default job parameters 156 int result = mBackend.nativeGetDefaultJobParameters(mJobParams); 157 if (result != 0) { 158 if (DEBUG) Log.w(TAG, "nativeGetDefaultJobParameters failure: " + result); 159 return Backend.ERROR_UNKNOWN; 160 } 161 162 if (isCancelled()) { 163 return Backend.ERROR_CANCEL; 164 } 165 166 // Fill in job parameters from capabilities and print job info. 167 populateJobParams(); 168 try (PdfRenderer renderer = new PdfRenderer( 169 ParcelFileDescriptor.open(pdfFile, ParcelFileDescriptor.MODE_READ_ONLY)); 170 PdfRenderer.Page page = renderer.openPage(0)) { 171 if (mJobParams.portrait_mode) { 172 mJobParams.source_height = (float) page.getHeight() / 72; 173 mJobParams.source_width = (float) page.getWidth() / 72; 174 } else { 175 mJobParams.source_width = (float) page.getHeight() / 72; 176 mJobParams.source_height = (float) page.getWidth() / 72; 177 } 178 } catch (IOException e) { 179 Log.w(TAG, "Error while getting source width, height", e); 180 } 181 182 // Finalize job parameters 183 mBackend.nativeGetFinalJobParameters(mJobParams, mCapabilities); 184 185 if (isCancelled()) { 186 return Backend.ERROR_CANCEL; 187 } 188 if (DEBUG) { 189 Log.d(TAG, "nativeStartJob address=" + address 190 + " port=" + mDestination.getPort() + " mime=" + MIME_TYPE_PDF 191 + " files=" + files[0] + " job=" + mJobParams); 192 } 193 // Initiate job 194 result = mBackend.nativeStartJob(Backend.getIp(address), mDestination.getPort(), 195 MIME_TYPE_PDF, mJobParams, mCapabilities, files, null, 196 mDestination.getScheme()); 197 if (result < 0) { 198 Log.w(TAG, "nativeStartJob failure: " + result); 199 return Backend.ERROR_UNKNOWN; 200 } 201 202 pdfFile = null; 203 return result; 204 } finally { 205 if (pdfFile != null) { 206 pdfFile.delete(); 207 } 208 } 209 } 210 isBorderless()211 private boolean isBorderless() { 212 return mCapabilities.borderless 213 && mDocInfo.getContentType() == PrintDocumentInfo.CONTENT_TYPE_PHOTO; 214 } 215 getSides()216 private int getSides() { 217 // Never duplex photo media; may damage printers 218 if (mDocInfo.getContentType() == PrintDocumentInfo.CONTENT_TYPE_PHOTO) { 219 return SIDES_SIMPLEX; 220 } 221 222 switch (mJobInfo.getAttributes().getDuplexMode()) { 223 case PrintAttributes.DUPLEX_MODE_LONG_EDGE: 224 return SIDES_DUPLEX_LONG_EDGE; 225 case PrintAttributes.DUPLEX_MODE_SHORT_EDGE: 226 return SIDES_DUPLEX_SHORT_EDGE; 227 case PrintAttributes.DUPLEX_MODE_NONE: 228 default: 229 return SIDES_SIMPLEX; 230 } 231 } 232 getFillPage()233 private boolean getFillPage() { 234 switch (mDocInfo.getContentType()) { 235 case PrintDocumentInfo.CONTENT_TYPE_PHOTO: 236 return true; 237 case PrintDocumentInfo.CONTENT_TYPE_UNKNOWN: 238 case PrintDocumentInfo.CONTENT_TYPE_DOCUMENT: 239 default: 240 return false; 241 } 242 } 243 getMediaType()244 private int getMediaType() { 245 int mediaType = MEDIA_TYPE_PLAIN; 246 for (int supportedType : mCapabilities.supportedMediaTypes) { 247 if (supportedType == MEDIA_TYPE_AUTO) { 248 // if auto media is supported, use that and break out of the loop 249 mediaType = MEDIA_TYPE_AUTO; 250 break; 251 } else if (mDocInfo.getContentType() == PrintDocumentInfo.CONTENT_TYPE_PHOTO 252 && supportedType > mediaType) { 253 // Select the best (highest #) supported type for photos 254 mediaType = supportedType; 255 } 256 } 257 return mediaType; 258 } 259 getColorSpace()260 private int getColorSpace() { 261 switch (mJobInfo.getAttributes().getColorMode()) { 262 case PrintAttributes.COLOR_MODE_COLOR: 263 return COLOR_SPACE_COLOR; 264 case PrintAttributes.COLOR_MODE_MONOCHROME: 265 default: 266 return COLOR_SPACE_MONOCHROME; 267 } 268 } 269 getDocumentCategory()270 private String getDocumentCategory() { 271 switch (mDocInfo.getContentType()) { 272 case PrintDocumentInfo.CONTENT_TYPE_PHOTO: 273 return BackendConstants.PRINT_DOCUMENT_CATEGORY__PHOTO; 274 275 case PrintDocumentInfo.CONTENT_TYPE_DOCUMENT: 276 default: 277 return BackendConstants.PRINT_DOCUMENT_CATEGORY__DOCUMENT; 278 } 279 } 280 isSharedPhoto()281 private boolean isSharedPhoto() { 282 return mJobInfo.getId().equals(ImagePrintActivity.getLastPrintJobId()); 283 } 284 } 285