340 lines
13 KiB
Java
340 lines
13 KiB
Java
/*
|
|
* 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();
|
|
}
|