• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.content.pm.PackageInfo;
22 import android.content.pm.PackageManager;
23 import android.net.Uri;
24 import android.os.AsyncTask;
25 import android.os.Build;
26 import android.os.Handler;
27 import android.printservice.PrintJob;
28 import android.text.TextUtils;
29 import android.util.Log;
30 
31 import com.android.bips.R;
32 import com.android.bips.jni.BackendConstants;
33 import com.android.bips.jni.JobCallback;
34 import com.android.bips.jni.JobCallbackParams;
35 import com.android.bips.jni.LocalJobParams;
36 import com.android.bips.jni.LocalPrinterCapabilities;
37 import com.android.bips.jni.PdfRender;
38 import com.android.bips.jni.PrinterStatusMonitor;
39 import com.android.bips.util.FileUtils;
40 
41 import java.io.File;
42 import java.util.Locale;
43 import java.util.function.Consumer;
44 
45 public class Backend implements JobCallback {
46     private static final String TAG = Backend.class.getSimpleName();
47     private static final boolean DEBUG = false;
48 
49     static final String TEMP_JOB_FOLDER = "jobs";
50 
51     // Error codes strictly to be in negative number
52     static final int ERROR_FILE = -1;
53     static final int ERROR_CANCEL = -2;
54     static final int ERROR_UNKNOWN = -3;
55 
56     private static final String VERSION_UNKNOWN = "(unknown)";
57 
58     private final Handler mMainHandler;
59     private final Context mContext;
60     private JobStatus mCurrentJobStatus;
61     private Consumer<JobStatus> mJobStatusListener;
62     private AsyncTask<Void, Void, Integer> mStartTask;
63 
Backend(Context context)64     public Backend(Context context) {
65         if (DEBUG) Log.d(TAG, "Backend()");
66 
67         mContext = context;
68         mMainHandler = new Handler(context.getMainLooper());
69         PdfRender.getInstance(mContext);
70 
71         // Load required JNI libraries
72         System.loadLibrary(BackendConstants.WPRINT_LIBRARY_PREFIX);
73 
74         // Create and initialize JNI layer
75         nativeInit(this, context.getApplicationInfo().dataDir, Build.VERSION.SDK_INT);
76         nativeSetSourceInfo(context.getString(R.string.app_name).toLowerCase(Locale.US),
77                 getApplicationVersion(context).toLowerCase(Locale.US),
78                 BackendConstants.WPRINT_APPLICATION_ID.toLowerCase(Locale.US));
79     }
80 
81     /** Return the current application version or VERSION_UNKNOWN */
getApplicationVersion(Context context)82     private String getApplicationVersion(Context context) {
83         try {
84             PackageInfo packageInfo = context.getPackageManager()
85                     .getPackageInfo(context.getPackageName(), 0);
86             return packageInfo.versionName;
87         } catch (PackageManager.NameNotFoundException e) {
88             return VERSION_UNKNOWN;
89         }
90     }
91 
92     /** Asynchronously get printer capabilities, returning results or null to a callback */
getCapabilities(Uri uri, long timeout, boolean highPriority, final Consumer<LocalPrinterCapabilities> capabilitiesConsumer)93     public GetCapabilitiesTask getCapabilities(Uri uri, long timeout, boolean highPriority,
94             final Consumer<LocalPrinterCapabilities> capabilitiesConsumer) {
95         if (DEBUG) Log.d(TAG, "getCapabilities()");
96 
97         GetCapabilitiesTask task = new GetCapabilitiesTask(this, uri, timeout, highPriority) {
98             @Override
99             protected void onPostExecute(LocalPrinterCapabilities result) {
100                 capabilitiesConsumer.accept(result);
101             }
102         };
103         task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
104         return task;
105     }
106 
107     /**
108      * Start a print job. Results will be notified to the listener. Do not start more than
109      * one job at a time.
110      */
print(Uri uri, PrintJob printJob, LocalPrinterCapabilities capabilities, Consumer<JobStatus> listener)111     public void print(Uri uri, PrintJob printJob, LocalPrinterCapabilities capabilities,
112             Consumer<JobStatus> listener) {
113         if (DEBUG) Log.d(TAG, "print()");
114 
115         mJobStatusListener = listener;
116         mCurrentJobStatus = new JobStatus();
117 
118         mStartTask = new StartJobTask(mContext, this, uri, printJob, capabilities) {
119             @Override
120             public void onCancelled(Integer result) {
121                 if (DEBUG) Log.d(TAG, "StartJobTask onCancelled " + result);
122                 onPostExecute(ERROR_CANCEL);
123             }
124 
125             @Override
126             protected void onPostExecute(Integer result) {
127                 if (DEBUG) Log.d(TAG, "StartJobTask onPostExecute " + result);
128                 mStartTask = null;
129                 if (result > 0) {
130                     mCurrentJobStatus = new JobStatus.Builder(mCurrentJobStatus).setId(result)
131                             .build();
132                 } else if (mJobStatusListener != null) {
133                     String jobResult = BackendConstants.JOB_DONE_ERROR;
134                     if (result == ERROR_CANCEL) {
135                         jobResult = BackendConstants.JOB_DONE_CANCELLED;
136                     } else if (result == ERROR_FILE) {
137                         jobResult = BackendConstants.JOB_DONE_CORRUPT;
138                     }
139 
140                     // If the start attempt failed and we are still listening, notify and be done
141                     mCurrentJobStatus = new JobStatus.Builder()
142                             .setJobState(BackendConstants.JOB_STATE_DONE)
143                             .setJobResult(jobResult).build();
144                     mJobStatusListener.accept(mCurrentJobStatus);
145                     mJobStatusListener = null;
146                 }
147             }
148         };
149         mStartTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
150     }
151 
152     /** Attempt to cancel the current job */
cancel()153     public void cancel() {
154         if (DEBUG) Log.d(TAG, "cancel()");
155 
156         if (mStartTask != null) {
157             if (DEBUG) Log.d(TAG, "cancelling start task");
158             mStartTask.cancel(true);
159         } else if (mCurrentJobStatus != null && mCurrentJobStatus.getId() != JobStatus.ID_UNKNOWN) {
160             if (DEBUG) Log.d(TAG, "cancelling job via new task");
161             new CancelJobTask(this, mCurrentJobStatus.getId())
162                     .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
163         } else {
164             if (DEBUG) Log.d(TAG, "Nothing to cancel in backend, ignoring");
165         }
166     }
167 
168     /**
169      * Call when it is safe to release document-centric resources related to a print job
170      */
closeDocument()171     public void closeDocument() {
172         // Tell the renderer it may release resources for the document
173         PdfRender.getInstance(mContext).closeDocument();
174     }
175 
176     /**
177      * Call when service is shutting down, nothing else is happening, and this object
178      * is no longer required. After closing this object it should be discarded.
179      */
close()180     public void close() {
181         nativeExit();
182         PdfRender.getInstance(mContext).close();
183     }
184 
185     /** Called by JNI */
186     @Override
jobCallback(final int jobId, final JobCallbackParams params)187     public void jobCallback(final int jobId, final JobCallbackParams params) {
188         mMainHandler.post(() -> {
189             if (DEBUG) Log.d(TAG, "jobCallback() jobId=" + jobId + ", params=" + params);
190 
191             JobStatus.Builder builder = new JobStatus.Builder(mCurrentJobStatus);
192 
193             builder.setId(params.jobId);
194 
195             if (params.certificate != null) {
196                 builder.setCertificate(params.certificate);
197             }
198 
199             if (!TextUtils.isEmpty(params.printerState)) {
200                 updateBlockedReasons(builder, params);
201             } else if (!TextUtils.isEmpty(params.jobState)) {
202                 builder.setJobState(params.jobState);
203                 if (!TextUtils.isEmpty(params.jobDoneResult)) {
204                     builder.setJobResult(params.jobDoneResult);
205                 }
206                 updateBlockedReasons(builder, params);
207             }
208             mCurrentJobStatus = builder.build();
209 
210             if (mJobStatusListener != null) {
211                 mJobStatusListener.accept(mCurrentJobStatus);
212             }
213 
214             if (mCurrentJobStatus.isJobDone()) {
215                 nativeEndJob(jobId);
216                 // Reset status for next job.
217                 mCurrentJobStatus = new JobStatus();
218                 mJobStatusListener = null;
219 
220                 FileUtils.deleteAll(new File(mContext.getFilesDir(), Backend.TEMP_JOB_FOLDER));
221             }
222         });
223     }
224 
225     /** Update the blocked reason list with non-empty strings */
updateBlockedReasons(JobStatus.Builder builder, JobCallbackParams params)226     private void updateBlockedReasons(JobStatus.Builder builder, JobCallbackParams params) {
227         if ((params.blockedReasons != null) && (params.blockedReasons.length > 0)) {
228             builder.clearBlockedReasons();
229             for (String reason : params.blockedReasons) {
230                 if (!TextUtils.isEmpty(reason)) {
231                     builder.addBlockedReason(reason);
232                 }
233             }
234         }
235     }
236 
237     /**
238      * Extracts the ip portion of x.x.x.x/y/z
239      *
240      * @param address any string in the format xxx/yyy/zzz
241      * @return the part before the "/" or "xxx" in this case
242      */
getIp(String address)243    public static String getIp(String address) {
244         int i = address.indexOf('/');
245         return i == -1 ? address : address.substring(0, i);
246     }
247 
248     /**
249      * Initialize the lower layer.
250      *
251      * @param jobCallback job callback to use whenever job updates arrive
252      * @param dataDir directory to use for temporary files
253      * @param apiVersion local system API version to be supplied to printers
254      * @return {@link BackendConstants#STATUS_OK} or an error code.
255      */
nativeInit(JobCallback jobCallback, String dataDir, int apiVersion)256     native int nativeInit(JobCallback jobCallback, String dataDir, int apiVersion);
257 
258     /**
259      * Supply additional information about the source of jobs.
260      *
261      * @param appName human-readable name of application providing data to the printer
262      * @param version version of delivering application
263      * @param appId identifier for the delivering application
264      */
nativeSetSourceInfo(String appName, String version, String appId)265     native void nativeSetSourceInfo(String appName, String version, String appId);
266 
267     /**
268      * Request capabilities from a printer.
269      *
270      * @param address IP address or hostname (e.g. "192.168.1.2")
271      * @param port port to use (e.g. 631)
272      * @param httpResource path of print resource on host (e.g. "/ipp/print")
273      * @param uriScheme scheme (e.g. "ipp")
274      * @param timeout milliseconds to wait before giving up on request
275      * @param capabilities target object to be filled with printer capabilities, if successful
276      * @return {@link BackendConstants#STATUS_OK} or an error code.
277      */
nativeGetCapabilities(String address, int port, String httpResource, String uriScheme, long timeout, LocalPrinterCapabilities capabilities)278     native int nativeGetCapabilities(String address, int port, String httpResource,
279             String uriScheme, long timeout, LocalPrinterCapabilities capabilities);
280 
281     /**
282      * Java native interface to setup to monitor status
283      *
284      * @param address      printer address
285      * @param port         printer port
286      * @param httpResource printer http resource
287      * @param uriScheme    printer URI scheme
288      * @return status handle
289      */
nativeMonitorStatusSetup( String address, int port, String httpResource, String uriScheme )290     public native long nativeMonitorStatusSetup(
291             String address, int port, String httpResource, String uriScheme
292     );
293 
294     /**
295      * Java native interface to start to monitor status
296      *
297      * @param statusId status ID
298      * @param monitor  print status monitor
299      */
nativeMonitorStatusStart(long statusId, PrinterStatusMonitor monitor)300     public native void nativeMonitorStatusStart(long statusId, PrinterStatusMonitor monitor);
301 
302     /**
303      * Java native interface to stop to monitor status
304      *
305      * @param statusId status ID
306      */
nativeMonitorStatusStop(long statusId)307     public native void nativeMonitorStatusStop(long statusId);
308 
309     /**
310      * Determine initial parameters to be used for jobs
311      *
312      * @param jobParams object to be filled with default parameters
313      * @return {@link BackendConstants#STATUS_OK} or an error code.
314      */
nativeGetDefaultJobParameters(LocalJobParams jobParams)315     native int nativeGetDefaultJobParameters(LocalJobParams jobParams);
316 
317     /**
318      * Update job parameters to align with known printer capabilities
319      *
320      * @param jobParams on input, contains requested job parameters; on output contains final
321      *                  job parameter selections.
322      * @param capabilities printer capabilities to be used when finalizing job parameters
323      * @return {@link BackendConstants#STATUS_OK} or an error code.
324      */
nativeGetFinalJobParameters(LocalJobParams jobParams, LocalPrinterCapabilities capabilities)325     native int nativeGetFinalJobParameters(LocalJobParams jobParams,
326             LocalPrinterCapabilities capabilities);
327 
328     /**
329      * Begin job delivery to a target printer. Updates on the job will be sent to the registered
330      * {@link JobCallback}.
331      *
332      * @param address IP address or hostname (e.g. "192.168.1.2")
333      * @param port port to use (e.g. 631)
334      * @param mimeType MIME type of data being sent
335      * @param jobParams job parameters to use when providing the job to the printer
336      * @param capabilities printer capabilities for the printer being used
337      * @param fileList list of files to be provided of the given MIME type
338      * @param debugDir directory to receive debugging information, if any
339      * @param scheme URI scheme (e.g. ipp/ipps)
340      * @return {@link BackendConstants#STATUS_OK} or an error code.
341      */
nativeStartJob(String address, int port, String mimeType, LocalJobParams jobParams, LocalPrinterCapabilities capabilities, String[] fileList, String debugDir, String scheme)342     native int nativeStartJob(String address, int port, String mimeType, LocalJobParams jobParams,
343             LocalPrinterCapabilities capabilities, String[] fileList, String debugDir,
344             String scheme);
345 
346     /**
347      * Request cancellation of the identified job.
348      *
349      * @param jobId identifier of the job to cancel
350      * @return {@link BackendConstants#STATUS_OK} or an error code.
351      */
nativeCancelJob(int jobId)352     native int nativeCancelJob(int jobId);
353 
354     /**
355      * Finalizes a job after it is ends for any reason
356      *
357      * @param jobId identifier of the job to end
358      * @return {@link BackendConstants#STATUS_OK} or an error code.
359      */
nativeEndJob(int jobId)360     native int nativeEndJob(int jobId);
361 
362     /**
363      * Shut down and clean up resources in the JNI layer on system exit
364      *
365      * @return {@link BackendConstants#STATUS_OK} or an error code.
366      */
nativeExit()367     native int nativeExit();
368 }
369