DO NOT MERGE - Merge PPRL.190305.001 into master
Bug: 127812889
Change-Id: I0dc244c1b48a3e1885cecac5202723af2866cd72
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..32d8056
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,30 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// 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.
+
+android_app {
+ name: "BuiltInPrintService",
+ srcs: [
+ "src/**/*.java",
+ "src/**/I*.aidl",
+ ],
+ resource_dirs: ["res"],
+ sdk_version: "system_current",
+ optimize: {
+ proguard_flags_files: ["proguard.flags"],
+ },
+ jni_libs: [
+ "libwfds",
+ "libcups",
+ ],
+}
diff --git a/Android.mk b/Android.mk
deleted file mode 100644
index beb246d..0000000
--- a/Android.mk
+++ /dev/null
@@ -1,34 +0,0 @@
-# Copyright (C) 2016 The Android Open Source Project
-#
-# 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.
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-Iaidl-files-under, src)
-
-LOCAL_RESOURCE_DIR += $(LOCAL_PATH)/res
-
-LOCAL_PACKAGE_NAME := BuiltInPrintService
-
-LOCAL_SDK_VERSION := system_current
-
-LOCAL_PROGUARD_FLAG_FILES := proguard.flags
-
-LOCAL_JNI_SHARED_LIBRARIES := libwfds libcups
-
-include $(BUILD_PACKAGE)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/jni/Android.bp b/jni/Android.bp
new file mode 100644
index 0000000..d1c9ef9
--- /dev/null
+++ b/jni/Android.bp
@@ -0,0 +1,71 @@
+// 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.
+
+cc_library_shared {
+ name: "libwfds",
+
+ sdk_version: "current",
+
+ cflags: [
+ "-DINCLUDE_PDF=1",
+ "-Werror",
+ "-Wextra",
+ "-Wno-unused-parameter",
+ "-Wno-sign-compare",
+ "-Wno-missing-field-initializers",
+ "-Wno-implicit-function-declaration",
+ "-Wno-format",
+ "-Wno-missing-braces",
+ ],
+
+ srcs: [
+ "lib/lib_wprint.c",
+ "lib/plugin_db.c",
+ "lib/printable_area.c",
+ "lib/printer.c",
+ "lib/wprint_msgq.c",
+ "lib/wprintJNI.c",
+
+ "ipphelper/ipp_print.c",
+ "ipphelper/ipphelper.c",
+ "ipphelper/ippstatus_capabilities.c",
+ "ipphelper/ippstatus_monitor.c",
+
+ "plugins/lib_pclm.c",
+ "plugins/lib_pwg.c",
+ "plugins/genPCLm/src/genPCLm.cpp",
+ "plugins/genPCLm/src/genJPEGStrips.cpp",
+ "plugins/pdf_render.c",
+ "plugins/plugin_pcl.c",
+ "plugins/plugin_pdf.c",
+ "plugins/pclm_wrapper_api.cpp",
+ "plugins/wprint_image.c",
+ "plugins/wprint_image_platform.c",
+ "plugins/wprint_mupdf.c",
+ "plugins/wprint_scaler.c",
+ ],
+
+ local_include_dirs: [
+ "include",
+ "plugins/genPCLm/inc",
+ "ipphelper",
+ ],
+ static_libs: ["libjpeg_static_ndk"],
+ shared_libs: [
+ "libcups",
+ "liblog",
+ "libz",
+ ],
+}
diff --git a/jni/Android.mk b/jni/Android.mk
deleted file mode 100644
index d57c115..0000000
--- a/jni/Android.mk
+++ /dev/null
@@ -1,60 +0,0 @@
-# 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.
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-INCLUDE_DIR := include
-LIB_DIR := lib
-PLUGINS_DIR := plugins
-IPP_HELPER_DIR := ipphelper
-
-LOCAL_SDK_VERSION := current
-
-LOCAL_CFLAGS += \
- -DINCLUDE_PDF=1 -Werror -Wextra -Wno-unused-parameter \
- -Wno-sign-compare -Wno-missing-field-initializers \
- -Wno-implicit-function-declaration -Wno-format -Wno-missing-braces
-
-PLUGINS_SRCS := \
- $(PLUGINS_DIR)/lib_pclm.c $(PLUGINS_DIR)/lib_pwg.c \
- $(PLUGINS_DIR)/genPCLm/src/genPCLm.cpp \
- $(PLUGINS_DIR)/genPCLm/src/genJPEGStrips.cpp \
- $(PLUGINS_DIR)/pdf_render.c $(PLUGINS_DIR)/plugin_pcl.c \
- $(PLUGINS_DIR)/plugin_pdf.c $(PLUGINS_DIR)/pclm_wrapper_api.cpp \
- $(PLUGINS_DIR)/wprint_image.c $(PLUGINS_DIR)/wprint_image_platform.c \
- $(PLUGINS_DIR)/wprint_mupdf.c $(PLUGINS_DIR)/wprint_scaler.c
-
-LIB_SRCS := \
- $(LIB_DIR)/lib_wprint.c $(LIB_DIR)/plugin_db.c \
- $(LIB_DIR)/printable_area.c $(LIB_DIR)/printer.c \
- $(LIB_DIR)/wprint_msgq.c $(LIB_DIR)/wprintJNI.c
-
-IPP_HELPER_SRCS := \
- $(IPP_HELPER_DIR)/ipp_print.c $(IPP_HELPER_DIR)/ipphelper.c \
- $(IPP_HELPER_DIR)/ippstatus_capabilities.c \
- $(IPP_HELPER_DIR)/ippstatus_monitor.c
-
-LOCAL_SRC_FILES:= \
- $(LIB_SRCS) $(IPP_HELPER_SRCS) $(PLUGINS_SRCS)
-
-LOCAL_C_INCLUDES += \
- $(LOCAL_PATH)/$(INCLUDE_DIR) $(LOCAL_PATH)/$(PLUGINS_DIR)/genPCLm/inc \
- $(LOCAL_PATH)/$(IPP_HELPER_DIR)
-LOCAL_STATIC_LIBRARIES := libjpeg_static_ndk
-LOCAL_SHARED_LIBRARIES := libcups liblog libz
-LOCAL_MODULE := libwfds
-LOCAL_MODULE_TAGS := optional
-include $(BUILD_SHARED_LIBRARY)
diff --git a/jni/include/lib_wprint.h b/jni/include/lib_wprint.h
index 006f6c1..0d2fd12 100644
--- a/jni/include/lib_wprint.h
+++ b/jni/include/lib_wprint.h
@@ -184,10 +184,15 @@
bool accepts_pclm;
bool accepts_pdf;
bool copies_supported;
+ int print_quality;
const char *useragent;
char docCategory[10];
const char *media_default;
+ // Expected certificate if any
+ uint8 *certificate;
+ int certificate_len;
+
// IPP max job-name is 2**31 - 1, we set a shorter limit
char job_name[MAX_ID_STRING_LENGTH + 1];
char job_originating_user_name[MAX_NAME_LENGTH + 1];
@@ -198,17 +203,23 @@
bool accepts_os_version;
} wprint_job_params_t;
+typedef struct wprint_connect_info_st wprint_connect_info_t;
+
/*
* Parameters defining how to reach a remote printing service
*/
-typedef struct {
+struct wprint_connect_info_st {
const char *printer_addr;
const char *uri_path;
const char *uri_scheme;
int port_num;
/* Timeout per retry in milliseconds */
long timeout;
-} wprint_connect_info_t;
+ /* Return non-0 if the received certificate is not acceptable. */
+ int (*validate_certificate)(struct wprint_connect_info_st *connect_info, uint8 *data, int data_len);
+ /* User-supplied data. */
+ void *user;
+};
/*
* Current state of a queued job
@@ -224,6 +235,9 @@
wprint_job_state_t state;
unsigned int blocked_reasons;
int job_done_result;
+ // Certificate received from printer, if any
+ uint8 *certificate;
+ int certificate_len;
} wprint_job_callback_params_t;
typedef enum {
diff --git a/jni/include/printer_capabilities_types.h b/jni/include/printer_capabilities_types.h
index 0f05b35..677f19c 100644
--- a/jni/include/printer_capabilities_types.h
+++ b/jni/include/printer_capabilities_types.h
@@ -22,6 +22,7 @@
#define MAX_MEDIA_TRAYS_SUPPORTED 10
#define MAX_MEDIA_TYPES_SUPPORTED 20
#define MAX_RESOLUTIONS_SUPPORTED 10
+#define MAX_QUALITY_SUPPORTED 3
#define MAX_URI_LENGTH 1024
#define MAX_STRING 256
#define MAX_UUID 46
@@ -43,6 +44,8 @@
char location[MAX_STRING];
unsigned char canRotateDuplexBackPage;
unsigned char color;
+ int supportedQuality[MAX_QUALITY_SUPPORTED];
+ unsigned int numSupportedQuality;
unsigned char faceDownTray;
media_size_t supportedMediaSizes[MAX_SIZES_SUPPORTED];
unsigned int numSupportedMediaSizes;
diff --git a/jni/include/wprint_status_types.h b/jni/include/wprint_status_types.h
index 9fe1b1d..80b3c74 100644
--- a/jni/include/wprint_status_types.h
+++ b/jni/include/wprint_status_types.h
@@ -38,7 +38,8 @@
#define BLOCKED_REASON_IDLE (1 << PRINT_STATUS_IDLE)
#define BLOCKED_REASON_CANCELLED (1 << PRINT_STATUS_CANCELLED)
#define BLOCKED_REASON_PRINT_STATUS_VERY_LOW_ON_INK (1 << PRINT_STATUS_VERY_LOW_ON_INK)
-#define BLOCKED_REASON_PARTIAL_CANCEL (1 << PRINT_STATUS_PARTIAL_CANCEL)
+#define BLOCKED_REASON_PARTIAL_CANCEL (1 << PRINT_STATUS_PARTIAL_CANCEL)
+#define BLOCKED_REASON_BAD_CERTIFICATE (1 << PRINT_STATUS_BAD_CERTIFICATE)
/*
* Enumeration for printer statuses
@@ -68,6 +69,7 @@
PRINT_STATUS_VERY_LOW_ON_INK,
PRINT_STATUS_PARTIAL_CANCEL,
+ PRINT_STATUS_BAD_CERTIFICATE,
PRINT_STATUS_MAX_STATE // Add new entries above this line.
} print_status_t;
diff --git a/jni/ipphelper/ipp_print.c b/jni/ipphelper/ipp_print.c
index 57914b3..36b7015 100644
--- a/jni/ipphelper/ipp_print.c
+++ b/jni/ipphelper/ipp_print.c
@@ -293,6 +293,12 @@
ippAddInteger(request, IPP_TAG_JOB, IPP_TAG_INTEGER, "copies", job_params->num_copies);
}
+ // Add print quality if requested
+ if (job_params->print_quality) {
+ ippAddInteger(request, IPP_TAG_JOB, IPP_TAG_ENUM, "print-quality",
+ job_params->print_quality);
+ }
+
ippAddResolution(request, IPP_TAG_JOB, "printer-resolution", IPP_RES_PER_INCH,
job_params->pixel_units, job_params->pixel_units);
if (job_params->duplex == DUPLEX_MODE_BOOK) {
diff --git a/jni/ipphelper/ipphelper.c b/jni/ipphelper/ipphelper.c
index 60758ac..f45060e 100644
--- a/jni/ipphelper/ipphelper.c
+++ b/jni/ipphelper/ipphelper.c
@@ -643,8 +643,6 @@
LOGD("media-supported found; number of values %d", ippGetCount(attrptr));
for (i = 0; i < ippGetCount(attrptr); i++) {
idx = ipp_find_media_size(ippGetString(attrptr, i, NULL), &media_sizeTemp);
- LOGD(" Temp - i: %d idx %d keyword: %s PT_size %d", i, idx, ippGetString(
- attrptr, i, NULL), media_sizeTemp);
// Modified since anytime the find media size returned 0 it could either mean
// NOT found or na_letter.
@@ -786,6 +784,15 @@
}
}
}
+ if ((attrptr = ippFindAttribute(response, "print-quality-supported", IPP_TAG_ENUM)) !=
+ NULL) {
+ for (i = 0; i < ippGetCount(attrptr) && capabilities->numSupportedQuality
+ < MAX_QUALITY_SUPPORTED; i++) {
+ LOGD("print-quality-supported: %d", ippGetInteger(attrptr, i));
+ capabilities->supportedQuality[capabilities->numSupportedQuality++] =
+ ippGetInteger(attrptr, i);
+ }
+ }
char imagePCLm[] = "application/PCLm";
char imagePWG[] = "image/pwg-raster";
@@ -1171,11 +1178,29 @@
}
}
+/*
+ * Handle server certificate information.
+ */
+static int ipp_server_cert_cb(http_t *http, void *tls, cups_array_t *certs, void *user_data) {
+ wprint_connect_info_t *connect_info = (wprint_connect_info_t *)user_data;
+ int error = 0;
+ if (connect_info->validate_certificate) {
+ http_credential_t *credential = cupsArrayFirst(certs);
+ if (credential) {
+ LOGD("ipp_server_cert_cb: validate_certificate (len=%d)", credential->datalen);
+ error = connect_info->validate_certificate(connect_info, credential->data, credential->datalen);
+ }
+ }
+ return error;
+}
+
http_t *ipp_cups_connect(const wprint_connect_info_t *connect_info, char *printer_uri,
unsigned int uriLength) {
const char *uri_path;
http_t *curl_http = NULL;
+ cupsSetServerCertCB(ipp_server_cert_cb, (void *)connect_info);
+
if ((connect_info->uri_path == NULL) || (strlen(connect_info->uri_path) == 0)) {
uri_path = DEFAULT_IPP_URI_RESOURCE;
} else {
@@ -1202,6 +1227,8 @@
if (curl_http == NULL) {
LOGD("ipp_cups_connect failed addr=%s port=%d", connect_info->printer_addr, ippPortNumber);
}
+
+ cupsSetServerCertCB(NULL, NULL);
return curl_http;
}
diff --git a/jni/ipphelper/ippstatus_capabilities.c b/jni/ipphelper/ippstatus_capabilities.c
index 2c34861..ad784d6 100644
--- a/jni/ipphelper/ippstatus_capabilities.c
+++ b/jni/ipphelper/ippstatus_capabilities.c
@@ -55,6 +55,7 @@
"media-type-supported",
"output-bin-supported",
"print-color-mode-supported",
+ "print-quality-supported",
"printer-resolution-supported",
"sides-supported",
"printer-device-id",
diff --git a/jni/lib/lib_wprint.c b/jni/lib/lib_wprint.c
index e7aebfe..b8bfbee 100644
--- a/jni/lib/lib_wprint.c
+++ b/jni/lib/lib_wprint.c
@@ -154,6 +154,10 @@
char printer_uri[1024];
int job_debug_fd;
int page_debug_fd;
+
+ /* A buffer of bytes containing the certificate received while setting up this job, if any. */
+ uint8 *certificate;
+ int certificate_len;
} _job_queue_t;
/*
@@ -480,7 +484,10 @@
}
jq->page_debug_fd = -1;
jq->debug_path[0] = 0;
-
+ if (jq->certificate) {
+ free(jq->certificate);
+ jq->certificate = NULL;
+ }
return OK;
} else {
return ERROR;
@@ -516,6 +523,8 @@
statusnew = new_status->printer_status & ~PRINTER_IDLE_BIT;
statusold = old_status->printer_status & ~PRINTER_IDLE_BIT;
+ cb_param.certificate = jq->certificate;
+ cb_param.certificate_len = jq->certificate_len;
LOGD("_job_status_callback(): current printer state: %d", statusnew);
blocked_reasons = 0;
@@ -689,10 +698,86 @@
}
/*
+ * Return true unless the server gave an unexpected certificate
+ */
+static bool _is_certificate_allowed(_job_queue_t *jq) {
+ int result = true;
+
+ // Compare certificates if both are known
+ if (jq->job_params.certificate && jq->certificate) {
+ if (jq->job_params.certificate_len != jq->certificate_len) {
+ LOGD("_is_certificate_allowed: certificate length mismatch allowed=%d, received=%d",
+ jq->job_params.certificate_len, jq->certificate_len);
+ result = false;
+ } else if (0 != memcmp(jq->job_params.certificate, jq->certificate, jq->certificate_len)) {
+ LOGD("_is_certificate_allowed: certificate content mismatch");
+ result = false;
+ } else {
+ LOGD("_is_certificate_allowed: certificate match, len=%d",
+ jq->job_params.certificate_len);
+ }
+ }
+
+ return result;
+}
+
+/*
+ * Callback from lower layers containing certificate data, if any.
+ */
+static int _validate_certificate(wprint_connect_info_t *connect_info, uint8 *data, int data_len) {
+ _job_queue_t *jq = connect_info->user;
+ LOGD("_validate_certificate: %s://%s:%d%s handling server cert len=%d for job %ld",
+ connect_info->uri_scheme, connect_info->printer_addr, connect_info->port_num,
+ connect_info->uri_path, data_len, jq->job_handle);
+
+ // Free any old certificate we have and save new certificate data
+ if (jq->certificate) {
+ free(jq->certificate);
+ jq->certificate = NULL;
+ }
+ jq->certificate = (uint8 *)malloc(data_len);
+ int error = 0;
+ if (jq->certificate == NULL) {
+ LOGD("_validate_certificate: malloc failed");
+ error = -1;
+ } else {
+ memcpy(jq->certificate, data, data_len);
+ jq->certificate_len = data_len;
+ if (!_is_certificate_allowed(jq)) {
+ LOGD("_validate_certificate: received certificate disallowed.");
+ error = -1;
+ }
+ }
+ return error;
+}
+
+/*
+ * Initialize the status interface (so we can use it to query for printer status.
+ */
+static void _initialize_status_ifc(_job_queue_t *jq) {
+ wprint_connect_info_t connect_info;
+ connect_info.printer_addr = jq->printer_addr;
+ connect_info.uri_path = jq->printer_uri;
+ connect_info.port_num = jq->port_num;
+ if (jq->use_secure_uri) {
+ connect_info.uri_scheme = IPPS_PREFIX;
+ connect_info.user = jq;
+ connect_info.validate_certificate = _validate_certificate;
+ } else {
+ connect_info.uri_scheme = IPP_PREFIX;
+ connect_info.validate_certificate = NULL;
+ }
+ connect_info.timeout = DEFAULT_IPP_TIMEOUT;
+
+ // Initialize the status interface with this connection info
+ jq->status_ifc->init(jq->status_ifc, &connect_info);
+}
+
+/*
* Runs a print job. Contains logic for what to do given different printer statuses.
*/
static void *_job_thread(void *param) {
- wprint_job_callback_params_t cb_param;
+ wprint_job_callback_params_t cb_param = { 0 };
_msg_t msg;
wJob_t job_handle;
_job_queue_t *jq;
@@ -737,17 +822,7 @@
// initialize the status ifc
if (jq->status_ifc != NULL) {
- wprint_connect_info_t connect_info;
- connect_info.printer_addr = jq->printer_addr;
- connect_info.uri_path = jq->printer_uri;
- connect_info.port_num = jq->port_num;
- if (jq->use_secure_uri) {
- connect_info.uri_scheme = IPPS_PREFIX;
- } else {
- connect_info.uri_scheme = IPP_PREFIX;
- }
- connect_info.timeout = DEFAULT_IPP_TIMEOUT;
- jq->status_ifc->init(jq->status_ifc, &connect_info);
+ _initialize_status_ifc(jq);
}
// wait for the printer to be idle
if ((jq->status_ifc != NULL) && (jq->status_ifc->get_status != NULL)) {
@@ -759,6 +834,10 @@
jq->status_ifc->get_status(jq->status_ifc, &printer_state);
status = printer_state.printer_status & ~PRINTER_IDLE_BIT;
+ // Pass along any certificate received in future callbacks
+ cb_param.certificate = jq->certificate;
+ cb_param.certificate_len = jq->certificate_len;
+
switch (status) {
case PRINT_STATUS_IDLE:
printer_state.printer_status = PRINT_STATUS_IDLE;
@@ -776,8 +855,13 @@
case PRINT_STATUS_SVC_REQUEST:
if ((printer_state.printer_reasons[0] == PRINT_STATUS_UNABLE_TO_CONNECT)
|| (printer_state.printer_reasons[0] == PRINT_STATUS_OFFLINE)) {
- LOGD("_job_thread: Received an Unable to Connect message");
- jq->blocked_reasons = BLOCKED_REASON_UNABLE_TO_CONNECT;
+ if (_is_certificate_allowed(jq)) {
+ LOGD("_job_thread: Received an Unable to Connect message");
+ jq->blocked_reasons = BLOCKED_REASON_UNABLE_TO_CONNECT;
+ } else {
+ LOGD("_job_thread: Bad certificate");
+ jq->blocked_reasons = BLOCKED_REASON_BAD_CERTIFICATE;
+ }
loop = 0;
break;
}
@@ -1371,6 +1455,16 @@
}
/*
+ * Return true if the specified int array of the supplied length contains a value.
+ */
+static bool int_array_contains(const int *array, int length, int value) {
+ for (int i = 0; i < length; i++) {
+ if (array[i] == value) return true;
+ }
+ return false;
+}
+
+/*
* Checks printers reported media sizes and validates that wprint supports them
*/
static void _validate_supported_media_sizes(printer_capabilities_t *printer_cap) {
@@ -1639,6 +1733,12 @@
job_params->num_copies = 1;
}
+ // If printing photo and HIGH quality is supported, specify it.
+ if (strcasecmp(job_params->docCategory, "photo") == 0 && int_array_contains(
+ printer_cap->supportedQuality, printer_cap->numSupportedQuality, IPP_QUALITY_HIGH)) {
+ job_params->print_quality = IPP_QUALITY_HIGH;
+ }
+
// confirm that the media size is supported
for (i = 0; i < printer_cap->numSupportedMediaSizes; i++) {
if (job_params->media_size == printer_cap->supportedMediaSizes[i]) {
@@ -2047,6 +2147,8 @@
cb_param.state = JOB_DONE;
cb_param.blocked_reasons = BLOCKED_REASONS_CANCELLED;
cb_param.job_done_result = CANCELLED;
+ cb_param.certificate = jq->certificate;
+ cb_param.certificate_len = jq->certificate_len;
jq->cb_fn(job_handle, (void *) &cb_param);
}
diff --git a/jni/lib/wprintJNI.c b/jni/lib/wprintJNI.c
index 913198a..42c27ca 100644
--- a/jni/lib/wprintJNI.c
+++ b/jni/lib/wprintJNI.c
@@ -75,6 +75,7 @@
static jfieldID _LocalPrinterCapabilitiesField__supportedMediaTypes;
static jfieldID _LocalPrinterCapabilitiesField__supportedMediaSizes;
static jfieldID _LocalPrinterCapabilitiesField__nativeData;
+static jfieldID _LocalPrinterCapabilitiesField__certificate;
static jclass _JobCallbackClass;
static jobject _callbackReceiver;
@@ -86,6 +87,7 @@
static jfieldID _JobCallbackParamsField__jobState;
static jfieldID _JobCallbackParamsField__jobDoneResult;
static jfieldID _JobCallbackParamsField__blockedReasons;
+static jfieldID _JobCallbackParamsField__certificate;
static jclass _PrintServiceStringsClass;
static jfieldID _PrintServiceStringsField__JOB_STATE_QUEUED;
@@ -110,6 +112,7 @@
static jfieldID _PrintServiceStringsField__BLOCKED_REASON__LOW_ON_INK;
static jfieldID _PrintServiceStringsField__BLOCKED_REASON__LOW_ON_TONER;
static jfieldID _PrintServiceStringsField__BLOCKED_REASON__REALLY_LOW_ON_INK;
+static jfieldID _PrintServiceStringsField__BLOCKED_REASON__BAD_CERTIFICATE;
static jfieldID _PrintServiceStringsField__BLOCKED_REASON__UNKNOWN;
static jfieldID _PrintServiceStringsField__ALIGNMENT__CENTER;
static jfieldID _PrintServiceStringsField__ALIGNMENT__CENTER_HORIZONTAL;
@@ -482,6 +485,8 @@
env, _LocalPrinterCapabilitiesClass, "supportedMediaSizes", "[I");
_LocalPrinterCapabilitiesField__nativeData = (*env)->GetFieldID(
env, _LocalPrinterCapabilitiesClass, "nativeData", "[B");
+ _LocalPrinterCapabilitiesField__certificate = (*env)->GetFieldID(
+ env, _LocalPrinterCapabilitiesClass, "certificate", "[B");
_JobCallbackParamsClass = (jclass) (*env)->NewGlobalRef(env, (*env)->FindClass(
env, "com/android/bips/jni/JobCallbackParams"));
@@ -495,6 +500,8 @@
env, _JobCallbackParamsClass, "jobDoneResult", "Ljava/lang/String;");
_JobCallbackParamsField__blockedReasons = (*env)->GetFieldID(
env, _JobCallbackParamsClass, "blockedReasons", "[Ljava/lang/String;");
+ _JobCallbackParamsField__certificate = (*env)->GetFieldID(
+ env, _JobCallbackParamsClass, "certificate", "[B");
if (callbackReceiver) {
_callbackReceiver = (jobject) (*env)->NewGlobalRef(env, callbackReceiver);
@@ -557,6 +564,9 @@
_PrintServiceStringsField__BLOCKED_REASON__REALLY_LOW_ON_INK = (*env)->GetStaticFieldID(
env, _PrintServiceStringsClass, "BLOCKED_REASON__REALLY_LOW_ON_INK",
"Ljava/lang/String;");
+ _PrintServiceStringsField__BLOCKED_REASON__BAD_CERTIFICATE = (*env)->GetStaticFieldID(
+ env, _PrintServiceStringsClass, "BLOCKED_REASON__BAD_CERTIFICATE",
+ "Ljava/lang/String;");
_PrintServiceStringsField__BLOCKED_REASON__UNKNOWN = (*env)->GetStaticFieldID(
env, _PrintServiceStringsClass, "BLOCKED_REASON__UNKNOWN", "Ljava/lang/String;");
@@ -589,7 +599,7 @@
}
jbyte *nativeDataPtr = (*env)->GetByteArrayElements(env, nativeDataObject, NULL);
memcpy(wprintPrinterCaps, (const void *) nativeDataPtr, sizeof(printer_capabilities_t));
- (*env)->ReleaseByteArrayElements(env, nativeDataObject, nativeDataPtr, JNI_ABORT);
+ (*env)->ReleaseByteArrayElements(env, nativeDataObject, nativeDataPtr, 0);
return OK;
}
@@ -1076,6 +1086,10 @@
jStr = (jstring) (*env)->GetStaticObjectField(
env, _PrintServiceStringsClass,
_PrintServiceStringsField__BLOCKED_REASON__REALLY_LOW_ON_INK);
+ } else if (blocked_reasons & BLOCKED_REASON_BAD_CERTIFICATE) {
+ jStr = (jstring) (*env)->GetStaticObjectField(
+ env, _PrintServiceStringsClass,
+ _PrintServiceStringsField__BLOCKED_REASON__BAD_CERTIFICATE);
} else if (blocked_reasons & BLOCKED_REASON_UNKNOWN) {
jStr = (jstring) (*env)->GetStaticObjectField(
env, _PrintServiceStringsClass,
@@ -1094,6 +1108,24 @@
(*env)->SetIntField(env, callbackParams, _JobCallbackParamsField__jobId,
(jint) job_handle);
+
+ if (cb_param->certificate) {
+ LOGI("_wprint_callback_fn: copying certificate len=%d", cb_param->certificate_len);
+ jbyteArray certificate = (*env)->NewByteArray(env, cb_param->certificate_len);
+ jbyte *certificateBytes = (*env)->GetByteArrayElements(env, certificate, 0);
+ memcpy(certificateBytes, (const void *) cb_param->certificate,
+ cb_param->certificate_len);
+ (*env)->ReleaseByteArrayElements(env, certificate, certificateBytes, 0);
+ (*env)->SetObjectField(env, callbackParams, _JobCallbackParamsField__certificate,
+ certificate);
+ (*env)->DeleteLocalRef(env, certificate);
+ } else {
+ LOGI("_wprint_callback_fn: there is no certificate");
+ // No cert, set NULL
+ (*env)->SetObjectField(env, callbackParams, _JobCallbackParamsField__certificate,
+ NULL);
+ }
+
(*env)->CallVoidMethod(env, _callbackReceiver, _JobCallbackMethod__jobCallback,
(jint) job_handle, callbackParams);
(*env)->DeleteLocalRef(env, callbackParams);
@@ -1160,6 +1192,7 @@
connect_info.uri_scheme = copyToNewString(env, uriScheme);
connect_info.port_num = port;
connect_info.timeout = timeout;
+ connect_info.validate_certificate = NULL;
LOGI("nativeGetCapabilities for %s JNIenv is %p", connect_info.printer_addr, env);
@@ -1222,6 +1255,24 @@
}
/*
+ * Convert certificate (if present) from printer capabilities into job_params.
+ */
+static void _convertCertificate(JNIEnv *env, jobject printerCaps, wprint_job_params_t *params) {
+ params->certificate = NULL;
+ jbyteArray certificate = (jbyteArray) (*env)->GetObjectField(env, printerCaps,
+ _LocalPrinterCapabilitiesField__certificate);
+ if (certificate) {
+ params->certificate_len = (*env)->GetArrayLength(env, certificate);
+ params->certificate = malloc(params->certificate_len);
+ if (params->certificate) {
+ jbyte *certificateBytes = (*env)->GetByteArrayElements(env, certificate, NULL);
+ memcpy(params->certificate, certificateBytes, params->certificate_len);
+ (*env)->ReleaseByteArrayElements(env, certificate, certificateBytes, JNI_ABORT);
+ }
+ }
+}
+
+/*
* JNI call to wprint to start a print job. Takes connection params, job params, caps, and file
* array to complete the job
*/
@@ -1238,6 +1289,7 @@
_convertJobParams_to_C(env, jobParams, ¶ms);
_convertPrinterCaps_to_C(env, printerCaps, &caps);
+ _convertCertificate(env, printerCaps, ¶ms);
LOGD("nativeStartJob: After _convertJobParams_to_C: res=%d, name=%s",
params.pdf_render_resolution, params.job_name);
@@ -1360,6 +1412,9 @@
}
}
+ if (params.certificate) {
+ free(params.certificate);
+ }
(*env)->ReleaseStringUTFChars(env, mimeType, mimeTypeStr);
(*env)->ReleaseStringUTFChars(env, address, addressStr);
(*env)->ReleaseStringUTFChars(env, _fakeDir, dataDirStr);
diff --git a/jni/plugins/lib_pwg.c b/jni/plugins/lib_pwg.c
index 5f60a10..8e3e525 100644
--- a/jni/plugins/lib_pwg.c
+++ b/jni/plugins/lib_pwg.c
@@ -70,7 +70,6 @@
h->PageSize[1] = (int) h->cupsPageSize[1];
h->Separations = CUPS_TRUE;
h->TraySwitch = CUPS_TRUE;
- h->Tumble = CUPS_TRUE;
h->cupsWidth = pixel_width;
h->cupsHeight = pixel_height;
h->cupsBitsPerPixel = (monochrome ? 8 : 24);
@@ -219,12 +218,15 @@
if (duplex == DUPLEX_MODE_BOOK) {
job_info->pclm_page_info.duplexDisposition = duplex_longEdge;
header_pwg.Duplex = CUPS_TRUE;
+ header_pwg.Tumble = CUPS_FALSE;
} else if (duplex == DUPLEX_MODE_TABLET) {
job_info->pclm_page_info.duplexDisposition = duplex_shortEdge;
header_pwg.Duplex = CUPS_TRUE;
+ header_pwg.Tumble = CUPS_TRUE;
} else {
job_info->pclm_page_info.duplexDisposition = simplex;
header_pwg.Duplex = CUPS_FALSE;
+ header_pwg.Tumble = CUPS_FALSE;
}
job_info->pclm_page_info.mirrorBackside = false;
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
deleted file mode 100644
index 343cf1f..0000000
--- a/res/values-as/strings.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
- Copyright (C) 2016 The Android Open Source Project
-
- 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name" msgid="3551052199033657984">"ডিফ\'ল্ট প্ৰিণ্ট সেৱা"</string>
- <string name="printer_busy" msgid="8604311528104955859">"ব্যস্ত"</string>
- <string name="printer_out_of_paper" msgid="4882186432807703877">"কাগজ শেষ হৈছে"</string>
- <string name="printer_out_of_ink" msgid="7361897651097675464">"চিয়াহী শেষ হৈছে"</string>
- <string name="printer_out_of_toner" msgid="2077516357225364154">"ট\'নাৰ শেষ হৈছে"</string>
- <string name="printer_low_on_ink" msgid="3515015872393897705">"চিয়াহী কম আছে"</string>
- <string name="printer_low_on_toner" msgid="8807858294038587130">"ট\'নাৰ কম আছে"</string>
- <string name="printer_door_open" msgid="2446302931916940874">"প্ৰিণ্টাৰৰ দুৱাৰ খোল খাই আছে"</string>
- <string name="printer_jammed" msgid="5104099859384749499">"প্ৰিণ্টাৰত জাম লাগিছে"</string>
- <string name="printer_offline" msgid="9196864753298645066">"প্ৰিণ্টাৰ অফলাইন হৈ আছে"</string>
- <string name="printer_check" msgid="6428369671197132828">"প্ৰিণ্টাৰ পৰীক্ষা কৰক"</string>
- <string name="waiting_to_send" msgid="2022115199902822180">"পঠাবলৈ ৰৈ থকা হৈছে"</string>
- <string name="unreadable_input" msgid="2199948329556527912">"দস্তাবেজ পঢ়িবপৰা নগ\'ল"</string>
- <string name="media_size_l" msgid="164416542021598181">"L"</string>
- <string name="media_size_5x7in" msgid="1275128379533195285">"৫x৭ ইঞ্চি"</string>
- <string name="media_size_89x119mm" msgid="6828167243395807385">"৮৯x১১৯ মি. মি."</string>
- <string name="media_size_54x86mm" msgid="1301991206183343895">"৫৪x৮৬ মি. মি."</string>
- <string name="media_size_8x10in" msgid="1872576638522812402">"৮x১০ ইঞ্চি"</string>
- <string name="media_size_4x6in" msgid="3093276425529958253">"৪x৬ ইঞ্চি"</string>
- <string name="printer_description" msgid="8580767673213837142">"%1$s: %2$s"</string>
- <string name="title_activity_add_printer" msgid="9119216095769228566">"প্ৰিণ্টাৰ যোগ কৰক"</string>
- <string name="add_printer_by_ip" msgid="562864787592910327">"আইপি ঠিকনাৰ জৰিয়তে প্ৰিণ্টাৰ যোগ কৰক"</string>
- <string name="hostname_or_ip" msgid="3460546103553992915">"হ\'ষ্টৰ নাম বা আইপি ঠিকনা"</string>
- <string name="ip_hint" msgid="7939777481941979799">"192.168.0.4"</string>
- <string name="add" msgid="1950342261671100906">"যোগ দিয়ক"</string>
- <string name="add_named" msgid="9074106244018070583">"প্ৰিণ্টাৰ যোগ কৰক <xliff:g id="PRINTER">%1$s</xliff:g>"</string>
- <string name="no_printer_found" msgid="4777867380924351173">"এই ঠিকনাত কোনো প্ৰিণ্টাৰ পোৱা নগ\'ল"</string>
- <string name="printer_not_supported" msgid="281955849350938408">"এই প্ৰিণ্টাৰটো নচলে"</string>
- <string name="wifi_direct" msgid="4629404342852294985">"ৱাই-ফাই ডাইৰেক্ট"</string>
- <string name="find_wifi_direct" msgid="5270504288829123954">"ৱাই-ফাই ডাইৰেক্ট প্ৰিণ্টাৰ বিচাৰক"</string>
- <string name="wifi_direct_printers" msgid="541168032444693191">"ৱাই-ফাই ডাইৰেক্ট প্ৰিণ্টাৰ"</string>
- <string name="searching" msgid="2114018057619514587">"সন্ধান কৰি থকা হৈছে…"</string>
- <string name="connect_hint_text" msgid="587112503851044234">"আপোনাৰ প্ৰিণ্টাৰৰ সন্মুখৰ পেনেলত আপুনি এই সংযোগটো অনুমোদন কৰিবলগীয়া হ\'ব পাৰে"</string>
- <string name="connecting_to" msgid="2665161014972086194">"<xliff:g id="PRINTER">%1$s</xliff:g>ৰ লগত সংযোগ কৰি থকা হৈছে"</string>
- <string name="failed_printer_connection" msgid="4196305972749960362">"প্ৰিণ্টাৰৰ লগত সংযোগ স্থাপন কৰিব পৰা নগ\'ল"</string>
- <string name="failed_connection" msgid="8068661997318286575">"<xliff:g id="PRINTER">%1$s</xliff:g>ৰ লগত সংযোগ স্থাপন কৰিব পৰা নগ\'ল"</string>
- <string name="saved_printers" msgid="4567534965213125526">"ছেভ কৰি ৰখা প্ৰিণ্টাৰ"</string>
- <string name="forget" msgid="892068061425802502">"পাহৰক"</string>
- <string name="connects_via_wifi_direct" msgid="652300632780158437">"ৱাই-ফাই ডাইৰেক্টৰ জৰিয়তে সংযোগ কৰে"</string>
- <string name="connects_via_network" msgid="5990041581556733898">"<xliff:g id="IP_ADDRESS">%1$s</xliff:g> ঠিকনাত চলিত নেটৱৰ্কৰ জৰিয়তে সংযোগ কৰে"</string>
-</resources>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
deleted file mode 100644
index f927213..0000000
--- a/res/values-or/strings.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
- Copyright (C) 2016 The Android Open Source Project
-
- 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name" msgid="3551052199033657984">"ଡିଫଲ୍ଟ ପ୍ରିଣ୍ଟ ସେବା"</string>
- <string name="printer_busy" msgid="8604311528104955859">"ବ୍ୟସ୍ତ"</string>
- <string name="printer_out_of_paper" msgid="4882186432807703877">"ପେପର୍ ଶେଷ ହୋଇଯାଇଛି"</string>
- <string name="printer_out_of_ink" msgid="7361897651097675464">"ଇଙ୍କ ଶେଷ ହୋଇଯାଇଛି"</string>
- <string name="printer_out_of_toner" msgid="2077516357225364154">"ଟୋନର୍ ନାହିଁ"</string>
- <string name="printer_low_on_ink" msgid="3515015872393897705">"ଇଙ୍କ କମ୍ ଅଛି"</string>
- <string name="printer_low_on_toner" msgid="8807858294038587130">"ଟୋନର୍ କମ୍ ଅଛି"</string>
- <string name="printer_door_open" msgid="2446302931916940874">"ଡୋର୍ ଖୋଲା ଅଛି"</string>
- <string name="printer_jammed" msgid="5104099859384749499">"ଜାମ୍ ହୋଇଯାଇଛି"</string>
- <string name="printer_offline" msgid="9196864753298645066">"ଅଫଲାଇନ୍"</string>
- <string name="printer_check" msgid="6428369671197132828">"ପ୍ରିଣ୍ଟର୍ ଖୋଜନ୍ତୁ"</string>
- <string name="waiting_to_send" msgid="2022115199902822180">"ପଠାଇବାକୁ ଅପେକ୍ଷା କରାଯାଉଛି"</string>
- <string name="unreadable_input" msgid="2199948329556527912">"ଡକ୍ୟୁମେଣ୍ଟ ପଢ଼ିପାରିଲା ନାହିଁ"</string>
- <string name="media_size_l" msgid="164416542021598181">"L"</string>
- <string name="media_size_5x7in" msgid="1275128379533195285">"5x7 ଇଞ୍ଚ"</string>
- <string name="media_size_89x119mm" msgid="6828167243395807385">"89x119 ମିମି"</string>
- <string name="media_size_54x86mm" msgid="1301991206183343895">"54x86 ମିମି"</string>
- <string name="media_size_8x10in" msgid="1872576638522812402">"8x10 ଇଞ୍ଚ"</string>
- <string name="media_size_4x6in" msgid="3093276425529958253">"4x6 ଇଞ୍ଚ"</string>
- <string name="printer_description" msgid="8580767673213837142">"%1$s – %2$s"</string>
- <string name="title_activity_add_printer" msgid="9119216095769228566">"ପ୍ରିଣ୍ଟର୍ ଯୋଡ଼ନ୍ତୁ"</string>
- <string name="add_printer_by_ip" msgid="562864787592910327">"IP ଠିକଣା ମାଧ୍ୟମରେ ପ୍ରିଣ୍ଟର୍କୁ ଯୋଡ଼ନ୍ତୁ"</string>
- <string name="hostname_or_ip" msgid="3460546103553992915">"ହୋଷ୍ଟନେମ୍ କିମ୍ବା IP ଠିକଣା"</string>
- <string name="ip_hint" msgid="7939777481941979799">"192.168.0.4"</string>
- <string name="add" msgid="1950342261671100906">"ଯୋଡ଼ନ୍ତୁ"</string>
- <string name="add_named" msgid="9074106244018070583">"<xliff:g id="PRINTER">%1$s</xliff:g>କୁ ଯୋଡ଼ନ୍ତୁ"</string>
- <string name="no_printer_found" msgid="4777867380924351173">"ଏହି ଠିକଣାରେ କୌଣସି ପ୍ରିଣ୍ଟର ମିଳିଲା ନାହିଁ"</string>
- <string name="printer_not_supported" msgid="281955849350938408">"ପ୍ରିଣ୍ଟର୍ ସପୋର୍ଟ କରୁନାହିଁ"</string>
- <string name="wifi_direct" msgid="4629404342852294985">"ୱାଇ-ଫାଇ ଡାଇରେକ୍ଟ"</string>
- <string name="find_wifi_direct" msgid="5270504288829123954">"ୱାଇ-ଫାଇ ଡାଇରେକ୍ଟ ପ୍ରିଣ୍ଟର୍ ଖୋଜନ୍ତୁ"</string>
- <string name="wifi_direct_printers" msgid="541168032444693191">"ୱାଇ-ଫାଇ ଡାଇରେକ୍ଟ ପ୍ରିଣ୍ଟର୍"</string>
- <string name="searching" msgid="2114018057619514587">"ଖୋଜୁଛି…"</string>
- <string name="connect_hint_text" msgid="587112503851044234">"ପ୍ରିଣ୍ଟର୍ର ସାମ୍ନା ପ୍ୟାନେଲ୍ରୁ ଆପଣ ଏହି କନେକ୍ସନ୍କୁ ଅନୁମୋଦନ କରିପାରିବେ"</string>
- <string name="connecting_to" msgid="2665161014972086194">"<xliff:g id="PRINTER">%1$s</xliff:g> ସହିତ କନେକ୍ଟ ହେଉଛି"</string>
- <string name="failed_printer_connection" msgid="4196305972749960362">"ପ୍ରିଣ୍ଟର ସହିତ କନେକ୍ଟ ହେଲାନାହିଁ"</string>
- <string name="failed_connection" msgid="8068661997318286575">"<xliff:g id="PRINTER">%1$s</xliff:g> ସହିତ କନେକ୍ଟ ହେଲାନାହିଁ"</string>
- <string name="saved_printers" msgid="4567534965213125526">"ସେଭ୍ ହୋଇଥିବା ପ୍ରିଣ୍ଟର୍"</string>
- <string name="forget" msgid="892068061425802502">"ଭୁଲିଯାଆନ୍ତୁ"</string>
- <string name="connects_via_wifi_direct" msgid="652300632780158437">"ୱାଇ-ଫାଇ ଡାଇରେକ୍ଟ ମାଧ୍ୟମରେ କନେକ୍ଟ ହେବ"</string>
- <string name="connects_via_network" msgid="5990041581556733898">"<xliff:g id="IP_ADDRESS">%1$s</xliff:g>ରେ ବର୍ତ୍ତମାନର ନେଟ୍ୱର୍କ ମାଧ୍ୟମରେ କନେକ୍ଟ କରାଯାଇଥାଏ"</string>
-</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index eac63ce..2ff94c6 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -29,6 +29,8 @@
<string name="printer_door_open">Door open</string>
<string name="printer_jammed">Jammed</string>
<string name="printer_offline">Offline</string>
+ <string name="printer_bad_certificate">Bad certificate</string>
+ <string name="printer_not_encrypted">Not encrypted</string>
<string name="printer_check">Check printer</string>
<string name="waiting_to_send">Waiting to send</string>
@@ -81,5 +83,16 @@
<string name="connects_via_network">Connects via current network at
<xliff:g example="192.168.0.101" id="ip_address">%1$s</xliff:g>
</string>
+ <!-- Channel name for security-related notifications [CHAR LIMIT=40] -->
+ <string name="security">Security</string>
+ <!-- Message shown in notification if a printer presented a changes security certificate [CHAR LIMIT=UNLIMITED] -->
+ <string name="certificate_update_request">This printer provided a new security certificate, or another
+ device is impersonating it. Accept the new certificate?</string>
+ <!-- Message shown in notification if a printer no longer supports encryption [CHAR LIMIT=UNLIMITED] -->
+ <string name="not_encrypted_request">This printer no longer accepts encrypted jobs. Continue printing?</string>
+ <!-- Button label in a notification. This button accepts a printer security change [CHAR LIMIT=20] -->
+ <string name="accept">Accept</string>
+ <!-- Button label in a notification. This button rejects a printer security change [CHAR LIMIT=20] -->
+ <string name="reject">Reject</string>
</resources>
diff --git a/src/com/android/bips/BuiltInPrintService.java b/src/com/android/bips/BuiltInPrintService.java
index 2bb73ef..9519962 100644
--- a/src/com/android/bips/BuiltInPrintService.java
+++ b/src/com/android/bips/BuiltInPrintService.java
@@ -17,13 +17,20 @@
package com.android.bips;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.graphics.drawable.Icon;
import android.net.nsd.NsdManager;
import android.net.wifi.WifiManager;
import android.os.Handler;
+import android.print.PrinterId;
import android.printservice.PrintJob;
import android.printservice.PrintService;
import android.printservice.PrinterDiscoverySession;
@@ -40,6 +47,7 @@
import com.android.bips.discovery.P2pDiscovery;
import com.android.bips.ipp.Backend;
import com.android.bips.ipp.CapabilitiesCache;
+import com.android.bips.ipp.CertificateStore;
import com.android.bips.p2p.P2pMonitor;
import com.android.bips.p2p.P2pUtils;
import com.android.bips.util.BroadcastMonitor;
@@ -51,6 +59,17 @@
private static final boolean DEBUG = false;
private static final int IPPS_PRINTER_DELAY = 150;
private static final int P2P_DISCOVERY_DELAY = 1000;
+ private static final String CHANNEL_ID_SECURITY = "security";
+ private static final String TAG_CERTIFICATE_REQUEST =
+ BuiltInPrintService.class.getCanonicalName() + ".CERTIFICATE_REQUEST";
+ private static final String ACTION_CERTIFICATE_ACCEPT =
+ BuiltInPrintService.class.getCanonicalName() + ".CERTIFICATE_ACCEPT";
+ private static final String ACTION_CERTIFICATE_REJECT =
+ BuiltInPrintService.class.getCanonicalName() + ".CERTIFICATE_REJECT";
+ private static final String EXTRA_CERTIFICATE = "certificate";
+ private static final String EXTRA_PRINTER_ID = "printer-id";
+ private static final String EXTRA_PRINTER_UUID = "printer-uuid";
+ private static final int CERTIFICATE_REQUEST_ID = 1000;
// Present because local activities can bind, but cannot access this object directly
private static WeakReference<BuiltInPrintService> sInstance;
@@ -60,6 +79,7 @@
private Discovery mMdnsDiscovery;
private ManualDiscovery mManualDiscovery;
private CapabilitiesCache mCapabilitiesCache;
+ private CertificateStore mCertificateStore;
private JobQueue mJobQueue;
private Handler mMainHandler;
private Backend mBackend;
@@ -85,9 +105,11 @@
}
}
super.onCreate();
+ createNotificationChannel();
sInstance = new WeakReference<>(this);
mBackend = new Backend(this);
+ mCertificateStore = new CertificateStore(this);
mCapabilitiesCache = new CapabilitiesCache(this, mBackend,
CapabilitiesCache.DEFAULT_MAX_CONCURRENT);
mP2pMonitor = new P2pMonitor(this);
@@ -200,6 +222,13 @@
}
/**
+ * Return a store of certificate public keys for supporting trust-on-first-use.
+ */
+ public CertificateStore getCertificateStore() {
+ return mCertificateStore;
+ }
+
+ /**
* Return the main handler for posting {@link Runnable} objects to the main UI
*/
public Handler getMainHandler() {
@@ -241,4 +270,96 @@
mWifiLock.release();
}
}
+
+ /**
+ * Set up a channel for notifications.
+ */
+ private void createNotificationChannel() {
+ NotificationChannel channel = new NotificationChannel(CHANNEL_ID_SECURITY,
+ getString(R.string.security), NotificationManager.IMPORTANCE_HIGH);
+
+ NotificationManager manager = (NotificationManager) getSystemService(
+ Context.NOTIFICATION_SERVICE);
+ manager.createNotificationChannel(channel);
+ }
+
+ /**
+ * Notify the user of a certificate change (could be a MITM attack) and allow response.
+ *
+ * When certificate is null, the printer is being downgraded to no-encryption.
+ */
+ void notifyCertificateChange(String printerName, PrinterId printerId, String printerUuid,
+ byte[] certificate) {
+ String message;
+ if (certificate == null) {
+ message = getString(R.string.not_encrypted_request);
+ } else {
+ message = getString(R.string.certificate_update_request);
+ }
+
+ Intent rejectIntent = new Intent(this, BuiltInPrintService.class)
+ .setAction(ACTION_CERTIFICATE_REJECT)
+ .putExtra(EXTRA_PRINTER_ID, printerId);
+ PendingIntent pendingRejectIntent = PendingIntent.getService(this, CERTIFICATE_REQUEST_ID,
+ rejectIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+ Notification.Action rejectAction = new Notification.Action.Builder(
+ Icon.createWithResource(this, R.drawable.ic_printservice),
+ getString(R.string.reject), pendingRejectIntent).build();
+
+ PendingIntent deleteIntent = PendingIntent.getService(this, CERTIFICATE_REQUEST_ID,
+ rejectIntent, 0);
+
+ Intent acceptIntent = new Intent(this, BuiltInPrintService.class)
+ .setAction(ACTION_CERTIFICATE_ACCEPT)
+ .putExtra(EXTRA_PRINTER_UUID, printerUuid)
+ .putExtra(EXTRA_PRINTER_ID, printerId);
+ if (certificate != null) {
+ acceptIntent.putExtra(EXTRA_CERTIFICATE, certificate);
+ }
+ PendingIntent pendingAcceptIntent = PendingIntent.getService(this, CERTIFICATE_REQUEST_ID,
+ acceptIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+ Notification.Action acceptAction = new Notification.Action.Builder(
+ Icon.createWithResource(this, R.drawable.ic_printservice),
+ getString(R.string.accept), pendingAcceptIntent).build();
+
+ Notification notification = new Notification.Builder(this, CHANNEL_ID_SECURITY)
+ .setContentTitle(printerName)
+ .setSmallIcon(R.drawable.ic_printservice)
+ .setStyle(new Notification.BigTextStyle().bigText(message))
+ .setContentText(message)
+ .setAutoCancel(true)
+ .addAction(rejectAction)
+ .addAction(acceptAction)
+ .setDeleteIntent(deleteIntent)
+ .build();
+
+ NotificationManager manager = (NotificationManager) getSystemService(
+ Context.NOTIFICATION_SERVICE);
+ manager.notify(TAG_CERTIFICATE_REQUEST, CERTIFICATE_REQUEST_ID, notification);
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (DEBUG) Log.d(TAG, "Received action=" + intent.getAction());
+ if (ACTION_CERTIFICATE_ACCEPT.equals(intent.getAction())) {
+ byte[] certificate = intent.getByteArrayExtra(EXTRA_CERTIFICATE);
+ PrinterId printerId = intent.getParcelableExtra(EXTRA_PRINTER_ID);
+ String printerUuid = intent.getStringExtra(EXTRA_PRINTER_UUID);
+ if (certificate != null) {
+ mCertificateStore.put(printerUuid, certificate);
+ } else {
+ mCertificateStore.remove(printerUuid);
+ }
+ // Restart the job with the updated certificate in place
+ mJobQueue.restart(printerId);
+ } else if (ACTION_CERTIFICATE_REJECT.equals(intent.getAction())) {
+ // Cancel any job in certificate state for this uuid
+ PrinterId printerId = intent.getParcelableExtra(EXTRA_PRINTER_ID);
+ mJobQueue.cancel(printerId);
+ }
+ NotificationManager manager = (NotificationManager) getSystemService(
+ Context.NOTIFICATION_SERVICE);
+ manager.cancel(TAG_CERTIFICATE_REQUEST, CERTIFICATE_REQUEST_ID);
+ return START_NOT_STICKY;
+ }
}
diff --git a/src/com/android/bips/JobQueue.java b/src/com/android/bips/JobQueue.java
index 6b64b41..f34dcdf 100644
--- a/src/com/android/bips/JobQueue.java
+++ b/src/com/android/bips/JobQueue.java
@@ -18,13 +18,16 @@
package com.android.bips;
import android.print.PrintJobId;
+import android.print.PrinterId;
import java.util.ArrayList;
import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.CopyOnWriteArrayList;
/** Manages a job queue, ensuring only one job is printed at a time */
class JobQueue {
- private final List<LocalPrintJob> mJobs = new ArrayList<>();
+ private final List<LocalPrintJob> mJobs = new CopyOnWriteArrayList<>();
private LocalPrintJob mCurrent;
/** Queue a print job for printing at the next available opportunity */
@@ -33,6 +36,26 @@
startNextJob();
}
+ /** Cancel any previously queued job for a printer with the supplied ID. */
+ void cancel(PrinterId printerId) {
+ for (LocalPrintJob job : mJobs) {
+ if (printerId.equals(job.getPrintJob().getInfo().getPrinterId())) {
+ cancel(job.getPrintJobId());
+ }
+ }
+
+ if (mCurrent != null && printerId.equals(mCurrent.getPrintJob().getInfo().getPrinterId())) {
+ cancel(mCurrent.getPrintJobId());
+ }
+ }
+
+ /** Restart any blocked job for a printer with this ID. */
+ void restart(PrinterId printerId) {
+ if (mCurrent != null && printerId.equals(mCurrent.getPrintJob().getInfo().getPrinterId())) {
+ mCurrent.restart();
+ }
+ }
+
/** Cancel a previously queued job */
void cancel(PrintJobId id) {
// If a job hasn't started, kill it instantly.
@@ -44,7 +67,7 @@
}
}
- if (mCurrent.getPrintJobId().equals(id)) {
+ if (mCurrent != null && mCurrent.getPrintJobId().equals(id)) {
mCurrent.cancel();
}
}
diff --git a/src/com/android/bips/LocalPrintJob.java b/src/com/android/bips/LocalPrintJob.java
index ba0776c..adadd14 100644
--- a/src/com/android/bips/LocalPrintJob.java
+++ b/src/com/android/bips/LocalPrintJob.java
@@ -27,6 +27,7 @@
import com.android.bips.discovery.MdnsDiscovery;
import com.android.bips.ipp.Backend;
import com.android.bips.ipp.CapabilitiesCache;
+import com.android.bips.ipp.CertificateStore;
import com.android.bips.ipp.JobStatus;
import com.android.bips.jni.BackendConstants;
import com.android.bips.jni.LocalPrinterCapabilities;
@@ -42,6 +43,7 @@
CapabilitiesCache.OnLocalPrinterCapabilities {
private static final String TAG = LocalPrintJob.class.getSimpleName();
private static final boolean DEBUG = false;
+ private static final String IPPS_SCHEME = "ipps";
/** Maximum time to wait to find a printer before failing the job */
private static final int DISCOVERY_TIMEOUT = 2 * 60 * 1000;
@@ -51,8 +53,9 @@
private static final int STATE_DISCOVERY = 1;
private static final int STATE_CAPABILITIES = 2;
private static final int STATE_DELIVERING = 3;
- private static final int STATE_CANCEL = 4;
- private static final int STATE_DONE = 5;
+ private static final int STATE_SECURITY = 4;
+ private static final int STATE_CANCEL = 5;
+ private static final int STATE_DONE = 6;
private final BuiltInPrintService mPrintService;
private final PrintJob mPrintJob;
@@ -63,6 +66,8 @@
private Uri mPath;
private DelayedAction mDiscoveryTimeout;
private P2pPrinterConnection mConnection;
+ private LocalPrinterCapabilities mCapabilities;
+ private CertificateStore mCertificateStore;
/**
* Construct the object; use {@link #start(Consumer)} to begin job processing.
@@ -71,6 +76,7 @@
mPrintService = printService;
mBackend = backend;
mPrintJob = printJob;
+ mCertificateStore = mPrintService.getCertificateStore();
mState = STATE_INIT;
// Tell the job it is blocked (until start())
@@ -107,12 +113,24 @@
mPrintService.getDiscovery().start(this);
}
+ /**
+ * Restart the job if possible.
+ */
+ void restart() {
+ if (DEBUG) Log.d(TAG, "restart() " + mPrintJob + " in state " + mState);
+ if (mState == STATE_SECURITY) {
+ mCapabilities.certificate = mCertificateStore.get(mCapabilities.uuid);
+ deliver();
+ }
+ }
+
void cancel() {
if (DEBUG) Log.d(TAG, "cancel() " + mPrintJob + " in state " + mState);
switch (mState) {
case STATE_DISCOVERY:
case STATE_CAPABILITIES:
+ case STATE_SECURITY:
// Cancel immediately
mState = STATE_CANCEL;
finish(false, null);
@@ -156,6 +174,14 @@
mPrintService.getDiscovery().stop(this);
mState = STATE_CAPABILITIES;
mPath = printer.path;
+ // Upgrade to IPPS path if present
+ for (Uri path : printer.paths) {
+ if (IPPS_SCHEME.equals(path.getScheme())) {
+ mPath = path;
+ break;
+ }
+ }
+
mPrintService.getCapabilitiesCache().request(printer, true, this);
}
@@ -213,14 +239,36 @@
if (mDiscoveryTimeout != null) {
mDiscoveryTimeout.cancel();
}
+ mCapabilities = capabilities;
+ deliver();
+ }
+ }
+
+ private void deliver() {
+ if (mCapabilities.certificate != null && !IPPS_SCHEME.equals(mPath.getScheme())) {
+ mState = STATE_SECURITY;
+ mPrintJob.block(mPrintService.getString(R.string.printer_not_encrypted));
+ mPrintService.notifyCertificateChange(mCapabilities.name,
+ mPrintJob.getInfo().getPrinterId(), mCapabilities.uuid, null);
+ } else {
mState = STATE_DELIVERING;
mPrintJob.start();
- mBackend.print(mPath, mPrintJob, capabilities, this::handleJobStatus);
+ mBackend.print(mPath, mPrintJob, mCapabilities, this::handleJobStatus);
}
}
private void handleJobStatus(JobStatus jobStatus) {
if (DEBUG) Log.d(TAG, "onJobStatus() " + jobStatus);
+
+ byte[] certificate = jobStatus.getCertificate();
+ if (certificate != null && mCapabilities != null) {
+ // If there is no certificate, record this one
+ if (mCertificateStore.get(mCapabilities.uuid) == null) {
+ if (DEBUG) Log.d(TAG, "Recording new certificate");
+ mCertificateStore.put(mCapabilities.uuid, certificate);
+ }
+ }
+
switch (jobStatus.getJobState()) {
case BackendConstants.JOB_STATE_DONE:
switch (jobStatus.getJobResult()) {
@@ -236,7 +284,11 @@
break;
default:
// Job failed
- finish(false, null);
+ if (jobStatus.getBlockedReasonId() == R.string.printer_bad_certificate) {
+ handleBadCertificate(jobStatus);
+ } else {
+ finish(false, null);
+ }
break;
}
break;
@@ -260,6 +312,20 @@
}
}
+ private void handleBadCertificate(JobStatus jobStatus) {
+ byte[] certificate = jobStatus.getCertificate();
+
+ if (certificate == null) {
+ mPrintJob.fail(mPrintService.getString(R.string.printer_bad_certificate));
+ } else {
+ if (DEBUG) Log.d(TAG, "Certificate change detected.");
+ mState = STATE_SECURITY;
+ mPrintJob.block(mPrintService.getString(R.string.printer_bad_certificate));
+ mPrintService.notifyCertificateChange(mCapabilities.name,
+ mPrintJob.getInfo().getPrinterId(), mCapabilities.uuid, certificate);
+ }
+ }
+
/**
* Terminate the job, issuing appropriate notifications.
*
diff --git a/src/com/android/bips/discovery/DiscoveredPrinter.java b/src/com/android/bips/discovery/DiscoveredPrinter.java
index 5d452af..6a135b8 100644
--- a/src/com/android/bips/discovery/DiscoveredPrinter.java
+++ b/src/com/android/bips/discovery/DiscoveredPrinter.java
@@ -26,6 +26,8 @@
import java.io.IOException;
import java.io.StringWriter;
+import java.util.Collections;
+import java.util.List;
import java.util.Objects;
/** Represents a network-visible printer */
@@ -42,6 +44,9 @@
/** Resource path at which the print service can be reached */
public final Uri path;
+ /** All paths at which this this printer can be reached. Includes "path". */
+ public final List<Uri> paths;
+
/** Lazily-created printer id. */
private PrinterId mPrinterId;
@@ -50,14 +55,27 @@
*
* @param uuid Unique identification of the network printer, if known
* @param name Self-identified printer or service name
+ * @param paths One or more network paths at which the printer is currently available
+ * @param location Self-advertised location of the printer, if known
+ */
+ public DiscoveredPrinter(Uri uuid, String name, List<Uri> paths, String location) {
+ this.uuid = uuid;
+ this.name = name;
+ this.path = paths.get(0);
+ this.paths = Collections.unmodifiableList(paths);
+ this.location = location;
+ }
+
+ /**
+ * Construct minimal information about a network printer
+ *
+ * @param uuid Unique identification of the network printer, if known
+ * @param name Self-identified printer or service name
* @param path Network path at which the printer is currently available
* @param location Self-advertised location of the printer, if known
*/
public DiscoveredPrinter(Uri uuid, String name, Uri path, String location) {
- this.uuid = uuid;
- this.name = name;
- this.path = path;
- this.location = location;
+ this(uuid, name, Collections.singletonList(path), location);
}
/** Construct an object based on field values of an JSON object found next in the JsonReader */
@@ -91,6 +109,7 @@
this.uuid = uuid;
this.name = printerName;
this.path = path;
+ this.paths = Collections.singletonList(path);
this.location = location;
}
@@ -103,6 +122,18 @@
}
/**
+ * Return true if this printer has a secure (encrypted) path.
+ */
+ public boolean isSecure() {
+ for (Uri path : paths) {
+ if (path.getScheme().equals("ipps")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* Return a host string for the user to see (an IP address or hostname without port number)
*/
public String getHost() {
@@ -139,7 +170,7 @@
DiscoveredPrinter other = (DiscoveredPrinter) obj;
return Objects.equals(uuid, other.uuid)
&& Objects.equals(name, other.name)
- && Objects.equals(path, other.path)
+ && Objects.equals(paths, other.paths)
&& Objects.equals(location, other.location);
}
@@ -148,7 +179,7 @@
int result = 17;
result = 31 * result + name.hashCode();
result = 31 * result + (uuid != null ? uuid.hashCode() : 0);
- result = 31 * result + path.hashCode();
+ result = 31 * result + paths.hashCode();
result = 31 * result + (location != null ? location.hashCode() : 0);
return result;
}
diff --git a/src/com/android/bips/discovery/MultiDiscovery.java b/src/com/android/bips/discovery/MultiDiscovery.java
index d564db9..93f68b4 100644
--- a/src/com/android/bips/discovery/MultiDiscovery.java
+++ b/src/com/android/bips/discovery/MultiDiscovery.java
@@ -44,31 +44,50 @@
mChildListener = new Listener() {
@Override
public void onPrinterFound(DiscoveredPrinter printer) {
- printerFound(first(printer.getUri()));
+ printerFound(merged(printer.getUri()));
}
@Override
public void onPrinterLost(DiscoveredPrinter printer) {
// Merge remaining printer records, if any
- DiscoveredPrinter first = first(printer.getUri());
- if (first == null) {
+ DiscoveredPrinter remaining = merged(printer.getUri());
+ if (remaining == null) {
printerLost(printer.getUri());
} else {
- printerFound(first);
+ printerFound(remaining);
}
}
};
}
- /** For a given URI return the first matching record, based on discovery mechanism order */
- private DiscoveredPrinter first(Uri printerUri) {
+ /**
+ * For a given URI combine and return records with the same printerUri, based on discovery
+ * mechanism order.
+ */
+ private DiscoveredPrinter merged(Uri printerUri) {
+ DiscoveredPrinter combined = null;
+
for (Discovery discovery : getChildren()) {
- DiscoveredPrinter found = discovery.getPrinter(printerUri);
- if (found != null) {
- return found;
+ DiscoveredPrinter discovered = discovery.getPrinter(printerUri);
+ if (discovered != null) {
+ if (combined == null) {
+ combined = discovered;
+ } else {
+ // Merge all paths found, in order, without duplicates
+ List<Uri> allPaths = new ArrayList<>(combined.paths);
+ for (Uri path : discovered.paths) {
+ if (!allPaths.contains(path)) {
+ allPaths.add(path);
+ }
+ }
+ // Assemble a new printer containing paths.
+ combined = new DiscoveredPrinter(discovered.uuid, discovered.name, allPaths,
+ discovered.location);
+ }
}
}
- return null;
+
+ return combined;
}
@Override
diff --git a/src/com/android/bips/ipp/Backend.java b/src/com/android/bips/ipp/Backend.java
index 94d6a11..0e4affd 100644
--- a/src/com/android/bips/ipp/Backend.java
+++ b/src/com/android/bips/ipp/Backend.java
@@ -191,6 +191,10 @@
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)) {
diff --git a/src/com/android/bips/ipp/CapabilitiesCache.java b/src/com/android/bips/ipp/CapabilitiesCache.java
index dd4d258..7c361f1 100644
--- a/src/com/android/bips/ipp/CapabilitiesCache.java
+++ b/src/com/android/bips/ipp/CapabilitiesCache.java
@@ -1,6 +1,5 @@
/*
* 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.
@@ -23,7 +22,6 @@
import android.net.NetworkInfo;
import android.net.Uri;
import android.net.wifi.p2p.WifiP2pManager;
-import android.os.AsyncTask;
import android.text.TextUtils;
import android.util.Log;
import android.util.LruCache;
@@ -48,8 +46,7 @@
* with the ability to fetch them on cache misses. {@link #close} must be called when use
* is complete.
*/
-public class CapabilitiesCache extends LruCache<Uri, LocalPrinterCapabilities> implements
- AutoCloseable {
+public class CapabilitiesCache implements AutoCloseable {
private static final String TAG = CapabilitiesCache.class.getSimpleName();
private static final boolean DEBUG = false;
@@ -66,6 +63,9 @@
// Maximum time per retry before giving up on second pass. Must differ from FIRST_PASS_TIMEOUT.
private static final int SECOND_PASS_TIMEOUT = 8000;
+ // Underlying cache
+ private final LruCache<Uri, LocalPrinterCapabilities> mCache = new LruCache<>(CACHE_SIZE);
+
// Outstanding requests based on printer path
private final Map<Uri, Request> mRequests = new HashMap<>();
private final Set<Uri> mToEvict = new HashSet<>();
@@ -81,7 +81,6 @@
* @param maxConcurrent Maximum number of capabilities requests to make at any one time
*/
public CapabilitiesCache(BuiltInPrintService service, Backend backend, int maxConcurrent) {
- super(CACHE_SIZE);
if (DEBUG) Log.d(TAG, "CapabilitiesCache()");
mService = service;
@@ -96,7 +95,7 @@
// Evict specified device capabilities when P2P network is lost.
if (DEBUG) Log.d(TAG, "Evicting P2P " + mToEvictP2p);
for (Uri uri : mToEvictP2p) {
- remove(uri);
+ mCache.remove(uri);
}
mToEvictP2p.clear();
}
@@ -108,7 +107,7 @@
// Evict specified device capabilities when network is lost.
if (DEBUG) Log.d(TAG, "Evicting Wi-Fi " + mToEvict);
for (Uri uri : mToEvict) {
- remove(uri);
+ mCache.remove(uri);
}
mToEvict.clear();
}
@@ -173,7 +172,20 @@
* Returns capabilities for the specified printer, if known
*/
public LocalPrinterCapabilities get(DiscoveredPrinter printer) {
- return get(printer.path);
+ LocalPrinterCapabilities capabilities = mCache.get(printer.path);
+ // Populate certificate from store if possible
+ if (capabilities != null) {
+ capabilities.certificate = mService.getCertificateStore().get(capabilities.uuid);
+ }
+ return capabilities;
+ }
+
+ /**
+ * Remove capabilities corresponding to a Printer URI
+ * @return The removed capabilities, if any
+ */
+ public LocalPrinterCapabilities remove(Uri printerUri) {
+ return mCache.remove(printerUri);
}
/**
@@ -281,10 +293,11 @@
startNextRequest();
return;
} else {
- remove(printer.getUri());
+ mCache.remove(printer.getUri());
}
} else {
- put(printer.path, capabilities);
+ capabilities.certificate = mService.getCertificateStore().get(capabilities.uuid);
+ mCache.put(printer.path, capabilities);
}
LocalPrinterCapabilities result = capabilities;
diff --git a/src/com/android/bips/ipp/CertificateStore.java b/src/com/android/bips/ipp/CertificateStore.java
new file mode 100644
index 0000000..76e0868
--- /dev/null
+++ b/src/com/android/bips/ipp/CertificateStore.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * 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.util.JsonReader;
+import android.util.JsonWriter;
+import android.util.Log;
+
+import com.android.bips.BuiltInPrintService;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A persistent cache of certificate public keys known to be associated with certain printer
+ * UUIDs.
+ */
+public class CertificateStore {
+ private static final String TAG = CertificateStore.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ /** File location of the on-disk certificate store. */
+ private final File mStoreFile;
+
+ /** RAM-based store of certificates (UUID to certificate) */
+ private final Map<String, byte[]> mCertificates = new HashMap<>();
+
+ public CertificateStore(BuiltInPrintService service) {
+ mStoreFile = new File(service.getCacheDir(), getClass().getSimpleName() + ".json");
+ load();
+ }
+
+ /** Write a new, non-null certificate to the store. */
+ public void put(String uuid, byte[] certificate) {
+ byte[] oldCertificate = mCertificates.put(uuid, certificate);
+ if (oldCertificate == null || !Arrays.equals(oldCertificate, certificate)) {
+ // Cache the certificate for later
+ if (DEBUG) Log.d(TAG, "New certificate uuid=" + uuid + " len=" + certificate.length);
+ save();
+ }
+ }
+
+ /** Remove any certificate associated with the specified UUID. */
+ public void remove(String uuid) {
+ if (mCertificates.remove(uuid) != null) {
+ save();
+ }
+ }
+
+ /** Return the known certificate public key for a printer having the specified UUID, or null. */
+ public byte[] get(String uuid) {
+ return mCertificates.get(uuid);
+ }
+
+ /** Write to storage immediately. */
+ private void save() {
+ if (mStoreFile.exists()) {
+ mStoreFile.delete();
+ }
+
+ try (JsonWriter writer = new JsonWriter(new BufferedWriter(new FileWriter(mStoreFile)))) {
+ writer.beginObject();
+ writer.name("certificates");
+ writer.beginArray();
+ for (Map.Entry<String, byte[]> entry : mCertificates.entrySet()) {
+ writer.beginObject();
+ writer.name("uuid").value(entry.getKey());
+ writer.name("pubkey").value(bytesToHex(entry.getValue()));
+ writer.endObject();
+ }
+ writer.endArray();
+ writer.endObject();
+ if (DEBUG) Log.d(TAG, "Wrote " + mCertificates.size() + " certificates to store");
+ } catch (NullPointerException | IOException e) {
+ Log.w(TAG, "Error while storing to " + mStoreFile, e);
+ }
+ }
+
+ /** Load known certificates from storage into RAM. */
+ private void load() {
+ if (!mStoreFile.exists()) {
+ return;
+ }
+
+ try (JsonReader reader = new JsonReader(new BufferedReader(new FileReader(mStoreFile)))) {
+ reader.beginObject();
+ while (reader.hasNext()) {
+ String itemName = reader.nextName();
+ if (itemName.equals("certificates")) {
+ reader.beginArray();
+ while (reader.hasNext()) {
+ loadItem(reader);
+ }
+ reader.endArray();
+ } else {
+ reader.skipValue();
+ }
+ }
+ reader.endObject();
+ } catch (IllegalStateException | IOException error) {
+ Log.w(TAG, "Error while loading from " + mStoreFile, error);
+ }
+ if (DEBUG) Log.d(TAG, "Loaded size=" + mCertificates.size() + " from " + mStoreFile);
+ }
+
+ /** Load a single certificate entry into RAM. */
+ private void loadItem(JsonReader reader) throws IOException {
+ String uuid = null;
+ byte[] pubkey = null;
+ reader.beginObject();
+ while (reader.hasNext()) {
+ String itemName = reader.nextName();
+ switch(itemName) {
+ case "uuid":
+ uuid = reader.nextString();
+ break;
+ case "pubkey":
+ try {
+ pubkey = hexToBytes(reader.nextString());
+ } catch (IllegalArgumentException ignored) {
+ }
+ break;
+ default:
+ reader.skipValue();
+ }
+ }
+ reader.endObject();
+ if (uuid != null && pubkey != null) {
+ mCertificates.put(uuid, pubkey);
+ }
+ }
+
+ private static final char[] HEX_CHARS = "0123456789ABCDEF".toCharArray();
+
+ /** Converts a byte array to a hexadecimal string, or null if bytes are null. */
+ private static String bytesToHex(byte[] bytes) {
+ if (bytes == null) {
+ return null;
+ }
+
+ char[] hexChars = new char[bytes.length * 2];
+ for (int i = 0; i < bytes.length; i++) {
+ int b = bytes[i] & 0xFF;
+ hexChars[i * 2] = HEX_CHARS[b >>> 4];
+ hexChars[i * 2 + 1] = HEX_CHARS[b & 0x0F];
+ }
+ return new String(hexChars);
+ }
+
+ /** Converts a hexadecimal string to a byte array, or null if hexString is null. */
+ private static byte[] hexToBytes(String hexString) {
+ if (hexString == null) {
+ return null;
+ }
+
+ char[] source = hexString.toCharArray();
+ byte[] dest = new byte[source.length / 2];
+ for (int sourcePos = 0, destPos = 0; sourcePos < source.length; ) {
+ int hi = Character.digit(source[sourcePos++], 16);
+ int lo = Character.digit(source[sourcePos++], 16);
+ if ((hi < 0) || (lo < 0)) {
+ throw new IllegalArgumentException();
+ }
+ dest[destPos++] = (byte) (hi << 4 | lo);
+ }
+ return dest;
+ }
+}
diff --git a/src/com/android/bips/ipp/JobStatus.java b/src/com/android/bips/ipp/JobStatus.java
index 72be347..b2d07e2 100644
--- a/src/com/android/bips/ipp/JobStatus.java
+++ b/src/com/android/bips/ipp/JobStatus.java
@@ -53,12 +53,15 @@
R.string.printer_low_on_toner);
sBlockReasonsMap.put(BackendConstants.BLOCKED_REASON__BUSY, R.string.printer_busy);
sBlockReasonsMap.put(BackendConstants.BLOCKED_REASON__OFFLINE, R.string.printer_offline);
+ sBlockReasonsMap.put(BackendConstants.BLOCKED_REASON__BAD_CERTIFICATE,
+ R.string.printer_bad_certificate);
}
private int mId;
private String mJobState;
private String mJobResult;
private final Set<String> mBlockedReasons;
+ private byte[] mCertificate;
/** Create a new, blank job status */
public JobStatus() {
@@ -72,6 +75,7 @@
mJobState = other.mJobState;
mJobResult = other.mJobResult;
mBlockedReasons = other.mBlockedReasons;
+ mCertificate = other.mCertificate;
}
/** Returns a string resource ID corresponding to a blocked reason, or 0 if none found */
@@ -104,12 +108,18 @@
return !TextUtils.isEmpty(mJobResult);
}
+ /** Return certificate if supplied as part of this status. */
+ public byte[] getCertificate() {
+ return mCertificate;
+ }
+
@Override
public String toString() {
return "JobStatus{id=" + mId
+ ", jobState=" + mJobState
+ ", jobResult=" + mJobResult
+ ", blockedReasons=" + mBlockedReasons
+ + ", certificate=" + (mCertificate != null)
+ "}";
}
@@ -139,6 +149,11 @@
return this;
}
+ Builder setCertificate(byte[] certificate) {
+ mPrototype.mCertificate = certificate;
+ return this;
+ }
+
Builder clearBlockedReasons() {
mPrototype.mBlockedReasons.clear();
return this;
diff --git a/src/com/android/bips/jni/BackendConstants.java b/src/com/android/bips/jni/BackendConstants.java
index a734a46..9960a47 100644
--- a/src/com/android/bips/jni/BackendConstants.java
+++ b/src/com/android/bips/jni/BackendConstants.java
@@ -67,5 +67,6 @@
public static final String BLOCKED_REASON__LOW_ON_INK = "marker-ink-almost-empty";
public static final String BLOCKED_REASON__LOW_ON_TONER = "marker-toner-almost-empty";
public static final String BLOCKED_REASON__REALLY_LOW_ON_INK = "marker-ink-really-low";
+ public static final String BLOCKED_REASON__BAD_CERTIFICATE = "bad-certificate";
public static final String BLOCKED_REASON__UNKNOWN = "unknown";
}
diff --git a/src/com/android/bips/jni/JobCallbackParams.java b/src/com/android/bips/jni/JobCallbackParams.java
index 97badec..f0b9aee 100644
--- a/src/com/android/bips/jni/JobCallbackParams.java
+++ b/src/com/android/bips/jni/JobCallbackParams.java
@@ -24,4 +24,5 @@
public String jobState;
public String jobDoneResult;
public String[] blockedReasons;
+ public byte[] certificate;
}
diff --git a/src/com/android/bips/jni/LocalPrinterCapabilities.java b/src/com/android/bips/jni/LocalPrinterCapabilities.java
index f30b24a..332ce5d 100644
--- a/src/com/android/bips/jni/LocalPrinterCapabilities.java
+++ b/src/com/android/bips/jni/LocalPrinterCapabilities.java
@@ -54,6 +54,9 @@
/** Bears the underlying native C structure (printer_capabilities_t) or null if not present */
public byte[] nativeData;
+ /** Public key of certificate for this printer, if known */
+ public byte[] certificate;
+
public void buildCapabilities(BuiltInPrintService service,
PrinterCapabilitiesInfo.Builder builder) {
builder.setColorModes(
@@ -122,6 +125,7 @@
+ " supportedMediaTypes=" + Arrays.toString(supportedMediaTypes)
+ " supportedMediaSizes=" + Arrays.toString(supportedMediaSizes)
+ " inetAddress=" + inetAddress
+ + " certificate=" + (certificate != null)
+ "}";
}
}