blob: 93e6271319f6fa76c912e0431c2584d2319f7aa6 [file] [log] [blame]
/*
* Copyright (C) 2017 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.printservice.recommendation.plugin.google;
import static com.android.printservice.recommendation.util.MDNSUtils.ATTRIBUTE_TY;
import android.content.Context;
import android.util.ArrayMap;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import com.android.printservice.recommendation.PrintServicePlugin;
import com.android.printservice.recommendation.R;
import com.android.printservice.recommendation.util.MDNSFilteredDiscovery;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Plugin detecting <a href="https://developers.google.com/cloud-print/docs/privet">Google Cloud
* Print</a> printers.
*/
public class CloudPrintPlugin implements PrintServicePlugin {
private static final String LOG_TAG = CloudPrintPlugin.class.getSimpleName();
private static final boolean DEBUG = false;
private static final String ATTRIBUTE_TXTVERS = "txtvers";
private static final String ATTRIBUTE_URL = "url";
private static final String ATTRIBUTE_TYPE = "type";
private static final String ATTRIBUTE_ID = "id";
private static final String ATTRIBUTE_CS = "cs";
private static final String TYPE = "printer";
private static final String PRIVET_SERVICE = "_privet._tcp";
/** The required mDNS service types */
private static final Set<String> PRINTER_SERVICE_TYPE = new HashSet<String>() {{
// Not checking _printer_._sub
add(PRIVET_SERVICE);
}};
/** All possible connection states */
private static final Set<String> POSSIBLE_CONNECTION_STATES = new HashSet<String>() {{
add("online");
add("offline");
add("connecting");
add("not-configured");
}};
private static final byte SUPPORTED_TXTVERS = '1';
/** The mDNS filtered discovery */
private final MDNSFilteredDiscovery mMDNSFilteredDiscovery;
/**
* Create a plugin detecting Google Cloud Print printers.
*
* @param context The context the plugin runs in
*/
public CloudPrintPlugin(@NonNull Context context) {
mMDNSFilteredDiscovery = new MDNSFilteredDiscovery(context, PRINTER_SERVICE_TYPE,
nsdServiceInfo -> {
// The attributes are case insensitive. For faster searching create a clone of
// the map with the attribute-keys all in lower case.
ArrayMap<String, byte[]> caseInsensitiveAttributes =
new ArrayMap<>(nsdServiceInfo.getAttributes().size());
for (Map.Entry<String, byte[]> entry : nsdServiceInfo.getAttributes()
.entrySet()) {
caseInsensitiveAttributes.put(entry.getKey().toLowerCase(),
entry.getValue());
}
if (DEBUG) {
Log.i(LOG_TAG, nsdServiceInfo.getServiceName() + ":");
Log.i(LOG_TAG, "type: " + nsdServiceInfo.getServiceType());
Log.i(LOG_TAG, "host: " + nsdServiceInfo.getHost());
for (Map.Entry<String, byte[]> entry : caseInsensitiveAttributes.entrySet()) {
if (entry.getValue() == null) {
Log.i(LOG_TAG, entry.getKey() + "= null");
} else {
Log.i(LOG_TAG, entry.getKey() + "=" + new String(entry.getValue(),
StandardCharsets.UTF_8));
}
}
}
byte[] txtvers = caseInsensitiveAttributes.get(ATTRIBUTE_TXTVERS);
if (txtvers == null || txtvers.length != 1 || txtvers[0] != SUPPORTED_TXTVERS) {
// The spec requires this to be the first attribute, but at this time we
// lost the order of the attributes
return false;
}
if (caseInsensitiveAttributes.get(ATTRIBUTE_TY) == null) {
return false;
}
byte[] url = caseInsensitiveAttributes.get(ATTRIBUTE_URL);
if (url == null || url.length == 0) {
return false;
}
byte[] type = caseInsensitiveAttributes.get(ATTRIBUTE_TYPE);
if (type == null || !TYPE.equals(
new String(type, StandardCharsets.UTF_8).toLowerCase())) {
return false;
}
if (caseInsensitiveAttributes.get(ATTRIBUTE_ID) == null) {
return false;
}
byte[] cs = caseInsensitiveAttributes.get(ATTRIBUTE_CS);
if (cs == null || !POSSIBLE_CONNECTION_STATES.contains(
new String(cs, StandardCharsets.UTF_8).toLowerCase())) {
return false;
}
InetAddress address = nsdServiceInfo.getHost();
if (!(address instanceof Inet4Address)) {
// Not checking for link local address
return false;
}
return true;
});
}
@Override
@NonNull public CharSequence getPackageName() {
return "com.google.android.apps.cloudprint";
}
@Override
public void start(@NonNull PrinterDiscoveryCallback callback) throws Exception {
mMDNSFilteredDiscovery.start(callback);
}
@Override
@StringRes public int getName() {
return R.string.plugin_vendor_google_cloud_print;
}
@Override
public void stop() throws Exception {
mMDNSFilteredDiscovery.stop();
}
}