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