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