| /* | |
| * 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.content.pm.PackageInfo; | |
| import android.content.pm.PackageManager; | |
| import android.net.Uri; | |
| import android.os.AsyncTask; | |
| import android.os.Build; | |
| import android.os.Handler; | |
| import android.printservice.PrintJob; | |
| import android.text.TextUtils; | |
| import android.util.Log; | |
| import com.android.bips.R; | |
| import com.android.bips.jni.BackendConstants; | |
| import com.android.bips.jni.JobCallback; | |
| import com.android.bips.jni.JobCallbackParams; | |
| import com.android.bips.jni.LocalJobParams; | |
| import com.android.bips.jni.LocalPrinterCapabilities; | |
| import com.android.bips.jni.PdfRender; | |
| import com.android.bips.util.FileUtils; | |
| import java.io.File; | |
| import java.util.Locale; | |
| import java.util.function.Consumer; | |
| public class Backend implements JobCallback { | |
| private static final String TAG = Backend.class.getSimpleName(); | |
| private static final boolean DEBUG = false; | |
| static final String TEMP_JOB_FOLDER = "jobs"; | |
| // Error codes strictly to be in negative number | |
| static final int ERROR_FILE = -1; | |
| static final int ERROR_CANCEL = -2; | |
| static final int ERROR_UNKNOWN = -3; | |
| private static final String VERSION_UNKNOWN = "(unknown)"; | |
| private final Handler mMainHandler; | |
| private final Context mContext; | |
| private JobStatus mCurrentJobStatus; | |
| private Consumer<JobStatus> mJobStatusListener; | |
| private AsyncTask<Void, Void, Integer> mStartTask; | |
| public Backend(Context context) { | |
| if (DEBUG) Log.d(TAG, "Backend()"); | |
| mContext = context; | |
| mMainHandler = new Handler(context.getMainLooper()); | |
| PdfRender.getInstance(mContext); | |
| // Load required JNI libraries | |
| System.loadLibrary(BackendConstants.WPRINT_LIBRARY_PREFIX); | |
| // Create and initialize JNI layer | |
| nativeInit(this, context.getApplicationInfo().dataDir, Build.VERSION.SDK_INT); | |
| nativeSetSourceInfo(context.getString(R.string.app_name).toLowerCase(Locale.US), | |
| getApplicationVersion(context).toLowerCase(Locale.US), | |
| BackendConstants.WPRINT_APPLICATION_ID.toLowerCase(Locale.US)); | |
| } | |
| /** Return the current application version or VERSION_UNKNOWN */ | |
| private String getApplicationVersion(Context context) { | |
| try { | |
| PackageInfo packageInfo = context.getPackageManager() | |
| .getPackageInfo(context.getPackageName(), 0); | |
| return packageInfo.versionName; | |
| } catch (PackageManager.NameNotFoundException e) { | |
| return VERSION_UNKNOWN; | |
| } | |
| } | |
| /** Asynchronously get printer capabilities, returning results or null to a callback */ | |
| public GetCapabilitiesTask getCapabilities(Uri uri, long timeout, boolean highPriority, | |
| final Consumer<LocalPrinterCapabilities> capabilitiesConsumer) { | |
| if (DEBUG) Log.d(TAG, "getCapabilities()"); | |
| GetCapabilitiesTask task = new GetCapabilitiesTask(this, uri, timeout, highPriority) { | |
| @Override | |
| protected void onPostExecute(LocalPrinterCapabilities result) { | |
| capabilitiesConsumer.accept(result); | |
| } | |
| }; | |
| task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); | |
| return task; | |
| } | |
| /** | |
| * Start a print job. Results will be notified to the listener. Do not start more than | |
| * one job at a time. | |
| */ | |
| public void print(Uri uri, PrintJob printJob, LocalPrinterCapabilities capabilities, | |
| Consumer<JobStatus> listener) { | |
| if (DEBUG) Log.d(TAG, "print()"); | |
| mJobStatusListener = listener; | |
| mCurrentJobStatus = new JobStatus(); | |
| mStartTask = new StartJobTask(mContext, this, uri, printJob, capabilities) { | |
| @Override | |
| public void onCancelled(Integer result) { | |
| if (DEBUG) Log.d(TAG, "StartJobTask onCancelled " + result); | |
| onPostExecute(ERROR_CANCEL); | |
| } | |
| @Override | |
| protected void onPostExecute(Integer result) { | |
| if (DEBUG) Log.d(TAG, "StartJobTask onPostExecute " + result); | |
| mStartTask = null; | |
| if (result > 0) { | |
| mCurrentJobStatus = new JobStatus.Builder(mCurrentJobStatus).setId(result) | |
| .build(); | |
| } else if (mJobStatusListener != null) { | |
| String jobResult = BackendConstants.JOB_DONE_ERROR; | |
| if (result == ERROR_CANCEL) { | |
| jobResult = BackendConstants.JOB_DONE_CANCELLED; | |
| } else if (result == ERROR_FILE) { | |
| jobResult = BackendConstants.JOB_DONE_CORRUPT; | |
| } | |
| // If the start attempt failed and we are still listening, notify and be done | |
| mCurrentJobStatus = new JobStatus.Builder() | |
| .setJobState(BackendConstants.JOB_STATE_DONE) | |
| .setJobResult(jobResult).build(); | |
| mJobStatusListener.accept(mCurrentJobStatus); | |
| mJobStatusListener = null; | |
| } | |
| } | |
| }; | |
| mStartTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); | |
| } | |
| /** Attempt to cancel the current job */ | |
| public void cancel() { | |
| if (DEBUG) Log.d(TAG, "cancel()"); | |
| if (mStartTask != null) { | |
| if (DEBUG) Log.d(TAG, "cancelling start task"); | |
| mStartTask.cancel(true); | |
| } else if (mCurrentJobStatus != null && mCurrentJobStatus.getId() != JobStatus.ID_UNKNOWN) { | |
| if (DEBUG) Log.d(TAG, "cancelling job via new task"); | |
| new CancelJobTask(this, mCurrentJobStatus.getId()) | |
| .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); | |
| } else { | |
| if (DEBUG) Log.d(TAG, "Nothing to cancel in backend, ignoring"); | |
| } | |
| } | |
| /** | |
| * Call when it is safe to release document-centric resources related to a print job | |
| */ | |
| public void closeDocument() { | |
| // Tell the renderer it may release resources for the document | |
| PdfRender.getInstance(mContext).closeDocument(); | |
| } | |
| /** | |
| * Call when service is shutting down, nothing else is happening, and this object | |
| * is no longer required. After closing this object it should be discarded. | |
| */ | |
| public void close() { | |
| new Thread(this::nativeExit).start(); | |
| PdfRender.getInstance(mContext).close(); | |
| } | |
| /** Called by JNI */ | |
| @Override | |
| public void jobCallback(final int jobId, final JobCallbackParams params) { | |
| mMainHandler.post(() -> { | |
| if (DEBUG) Log.d(TAG, "jobCallback() jobId=" + jobId + ", params=" + params); | |
| JobStatus.Builder builder = new JobStatus.Builder(mCurrentJobStatus); | |
| builder.setId(params.jobId); | |
| if (params.certificate != null) { | |
| builder.setCertificate(params.certificate); | |
| } | |
| if (!TextUtils.isEmpty(params.printerState)) { | |
| updateBlockedReasons(builder, params); | |
| } else if (!TextUtils.isEmpty(params.jobState)) { | |
| builder.setJobState(params.jobState); | |
| if (!TextUtils.isEmpty(params.jobDoneResult)) { | |
| builder.setJobResult(params.jobDoneResult); | |
| } | |
| updateBlockedReasons(builder, params); | |
| } | |
| mCurrentJobStatus = builder.build(); | |
| if (mJobStatusListener != null) { | |
| mJobStatusListener.accept(mCurrentJobStatus); | |
| } | |
| if (mCurrentJobStatus.isJobDone()) { | |
| nativeEndJob(jobId); | |
| // Reset status for next job. | |
| mCurrentJobStatus = new JobStatus(); | |
| mJobStatusListener = null; | |
| FileUtils.deleteAll(new File(mContext.getFilesDir(), Backend.TEMP_JOB_FOLDER)); | |
| } | |
| }); | |
| } | |
| /** Update the blocked reason list with non-empty strings */ | |
| private void updateBlockedReasons(JobStatus.Builder builder, JobCallbackParams params) { | |
| if ((params.blockedReasons != null) && (params.blockedReasons.length > 0)) { | |
| builder.clearBlockedReasons(); | |
| for (String reason : params.blockedReasons) { | |
| if (!TextUtils.isEmpty(reason)) { | |
| builder.addBlockedReason(reason); | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * Extracts the ip portion of x.x.x.x/y/z | |
| * | |
| * @param address any string in the format xxx/yyy/zzz | |
| * @return the part before the "/" or "xxx" in this case | |
| */ | |
| static String getIp(String address) { | |
| int i = address.indexOf('/'); | |
| return i == -1 ? address : address.substring(0, i); | |
| } | |
| /** | |
| * Initialize the lower layer. | |
| * | |
| * @param jobCallback job callback to use whenever job updates arrive | |
| * @param dataDir directory to use for temporary files | |
| * @param apiVersion local system API version to be supplied to printers | |
| * @return {@link BackendConstants#STATUS_OK} or an error code. | |
| */ | |
| native int nativeInit(JobCallback jobCallback, String dataDir, int apiVersion); | |
| /** | |
| * Supply additional information about the source of jobs. | |
| * | |
| * @param appName human-readable name of application providing data to the printer | |
| * @param version version of delivering application | |
| * @param appId identifier for the delivering application | |
| */ | |
| native void nativeSetSourceInfo(String appName, String version, String appId); | |
| /** | |
| * Request capabilities from a printer. | |
| * | |
| * @param address IP address or hostname (e.g. "192.168.1.2") | |
| * @param port port to use (e.g. 631) | |
| * @param httpResource path of print resource on host (e.g. "/ipp/print") | |
| * @param uriScheme scheme (e.g. "ipp") | |
| * @param timeout milliseconds to wait before giving up on request | |
| * @param capabilities target object to be filled with printer capabilities, if successful | |
| * @return {@link BackendConstants#STATUS_OK} or an error code. | |
| */ | |
| native int nativeGetCapabilities(String address, int port, String httpResource, | |
| String uriScheme, long timeout, LocalPrinterCapabilities capabilities); | |
| /** | |
| * Determine initial parameters to be used for jobs | |
| * | |
| * @param jobParams object to be filled with default parameters | |
| * @return {@link BackendConstants#STATUS_OK} or an error code. | |
| */ | |
| native int nativeGetDefaultJobParameters(LocalJobParams jobParams); | |
| /** | |
| * Update job parameters to align with known printer capabilities | |
| * | |
| * @param jobParams on input, contains requested job parameters; on output contains final | |
| * job parameter selections. | |
| * @param capabilities printer capabilities to be used when finalizing job parameters | |
| * @return {@link BackendConstants#STATUS_OK} or an error code. | |
| */ | |
| native int nativeGetFinalJobParameters(LocalJobParams jobParams, | |
| LocalPrinterCapabilities capabilities); | |
| /** | |
| * Begin job delivery to a target printer. Updates on the job will be sent to the registered | |
| * {@link JobCallback}. | |
| * | |
| * @param address IP address or hostname (e.g. "192.168.1.2") | |
| * @param port port to use (e.g. 631) | |
| * @param mimeType MIME type of data being sent | |
| * @param jobParams job parameters to use when providing the job to the printer | |
| * @param capabilities printer capabilities for the printer being used | |
| * @param fileList list of files to be provided of the given MIME type | |
| * @param debugDir directory to receive debugging information, if any | |
| * @param scheme URI scheme (e.g. ipp/ipps) | |
| * @return {@link BackendConstants#STATUS_OK} or an error code. | |
| */ | |
| native int nativeStartJob(String address, int port, String mimeType, LocalJobParams jobParams, | |
| LocalPrinterCapabilities capabilities, String[] fileList, String debugDir, | |
| String scheme); | |
| /** | |
| * Request cancellation of the identified job. | |
| * | |
| * @param jobId identifier of the job to cancel | |
| * @return {@link BackendConstants#STATUS_OK} or an error code. | |
| */ | |
| native int nativeCancelJob(int jobId); | |
| /** | |
| * Finalizes a job after it is ends for any reason | |
| * | |
| * @param jobId identifier of the job to end | |
| * @return {@link BackendConstants#STATUS_OK} or an error code. | |
| */ | |
| native int nativeEndJob(int jobId); | |
| /** | |
| * Shut down and clean up resources in the JNI layer on system exit | |
| * | |
| * @return {@link BackendConstants#STATUS_OK} or an error code. | |
| */ | |
| native int nativeExit(); | |
| } |