From 5a633c6e4d9661c574ac34e891da22ec62b5e383 Mon Sep 17 00:00:00 2001 From: "Philip P. Moltmann" Date: Thu, 9 Nov 2017 15:55:24 -0800 Subject: [PATCH] Don't show "always use" when app can't be default Each USB device can have one default handler. This app is launched when the device is plugged in. A default handler automatically gets permission to access the device when launched. All other apps have to request the permission to access the USB device via UsbManager.requestPermission. In the permission dialog there is an option to make the app the default app for the device. Of course this only makes sense if this app declares the right properties to be auto-launched. Hence check this and only then show this options. Also made the string in the dialogs easier to understand. AccessoryFilter and DeviceFilter needed to be copied so I can access them from system-ui. No code change in these classes. Test: requested permissions from app that could by the default for the device and from an app that could not. Also tested "confirm" flow that is executed when the device is plugged in and a app that might be default is installed. Bug: 67381583 Change-Id: I12eb7efed0ad107c70ae32433a5629108f252486 --- .../android/hardware/usb/AccessoryFilter.java | 145 +++++++ .../android/hardware/usb/DeviceFilter.java | 313 ++++++++++++++ packages/SystemUI/res/values/strings.xml | 14 +- .../systemui/usb/UsbConfirmActivity.java | 12 +- .../systemui/usb/UsbPermissionActivity.java | 139 +++++- .../usb/UsbProfileGroupSettingsManager.java | 403 +----------------- 6 files changed, 599 insertions(+), 427 deletions(-) create mode 100644 core/java/android/hardware/usb/AccessoryFilter.java create mode 100644 core/java/android/hardware/usb/DeviceFilter.java diff --git a/core/java/android/hardware/usb/AccessoryFilter.java b/core/java/android/hardware/usb/AccessoryFilter.java new file mode 100644 index 0000000000000..d9b7c5be7dddd --- /dev/null +++ b/core/java/android/hardware/usb/AccessoryFilter.java @@ -0,0 +1,145 @@ +/* + * Copyright 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 android.hardware.usb; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.util.Objects; + +/** + * This class is used to describe a USB accessory. + * When used in HashMaps all values must be specified, + * but wildcards can be used for any of the fields in + * the package meta-data. + * + * @hide + */ +public class AccessoryFilter { + // USB accessory manufacturer (or null for unspecified) + public final String mManufacturer; + // USB accessory model (or null for unspecified) + public final String mModel; + // USB accessory version (or null for unspecified) + public final String mVersion; + + public AccessoryFilter(String manufacturer, String model, String version) { + mManufacturer = manufacturer; + mModel = model; + mVersion = version; + } + + public AccessoryFilter(UsbAccessory accessory) { + mManufacturer = accessory.getManufacturer(); + mModel = accessory.getModel(); + mVersion = accessory.getVersion(); + } + + public static AccessoryFilter read(XmlPullParser parser) + throws XmlPullParserException, IOException { + String manufacturer = null; + String model = null; + String version = null; + + int count = parser.getAttributeCount(); + for (int i = 0; i < count; i++) { + String name = parser.getAttributeName(i); + String value = parser.getAttributeValue(i); + + if ("manufacturer".equals(name)) { + manufacturer = value; + } else if ("model".equals(name)) { + model = value; + } else if ("version".equals(name)) { + version = value; + } + } + return new AccessoryFilter(manufacturer, model, version); + } + + public void write(XmlSerializer serializer)throws IOException { + serializer.startTag(null, "usb-accessory"); + if (mManufacturer != null) { + serializer.attribute(null, "manufacturer", mManufacturer); + } + if (mModel != null) { + serializer.attribute(null, "model", mModel); + } + if (mVersion != null) { + serializer.attribute(null, "version", mVersion); + } + serializer.endTag(null, "usb-accessory"); + } + + public boolean matches(UsbAccessory acc) { + if (mManufacturer != null && !acc.getManufacturer().equals(mManufacturer)) return false; + if (mModel != null && !acc.getModel().equals(mModel)) return false; + return !(mVersion != null && !acc.getVersion().equals(mVersion)); + } + + /** + * Is the accessories described {@code accessory} covered by this filter? + * + * @param accessory A filter describing the accessory + * + * @return {@code true} iff this the filter covers the accessory + */ + public boolean contains(AccessoryFilter accessory) { + if (mManufacturer != null && !Objects.equals(accessory.mManufacturer, mManufacturer)) { + return false; + } + if (mModel != null && !Objects.equals(accessory.mModel, mModel)) return false; + return !(mVersion != null && !Objects.equals(accessory.mVersion, mVersion)); + } + + @Override + public boolean equals(Object obj) { + // can't compare if we have wildcard strings + if (mManufacturer == null || mModel == null || mVersion == null) { + return false; + } + if (obj instanceof AccessoryFilter) { + AccessoryFilter filter = (AccessoryFilter)obj; + return (mManufacturer.equals(filter.mManufacturer) && + mModel.equals(filter.mModel) && + mVersion.equals(filter.mVersion)); + } + if (obj instanceof UsbAccessory) { + UsbAccessory accessory = (UsbAccessory)obj; + return (mManufacturer.equals(accessory.getManufacturer()) && + mModel.equals(accessory.getModel()) && + mVersion.equals(accessory.getVersion())); + } + return false; + } + + @Override + public int hashCode() { + return ((mManufacturer == null ? 0 : mManufacturer.hashCode()) ^ + (mModel == null ? 0 : mModel.hashCode()) ^ + (mVersion == null ? 0 : mVersion.hashCode())); + } + + @Override + public String toString() { + return "AccessoryFilter[mManufacturer=\"" + mManufacturer + + "\", mModel=\"" + mModel + + "\", mVersion=\"" + mVersion + "\"]"; + } +} diff --git a/core/java/android/hardware/usb/DeviceFilter.java b/core/java/android/hardware/usb/DeviceFilter.java new file mode 100644 index 0000000000000..439c629758b04 --- /dev/null +++ b/core/java/android/hardware/usb/DeviceFilter.java @@ -0,0 +1,313 @@ +/* + * Copyright 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 android.hardware.usb; + +import android.util.Slog; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.util.Objects; + +/** + * This class is used to describe a USB device. + * When used in HashMaps all values must be specified, + * but wildcards can be used for any of the fields in + * the package meta-data. + * + * @hide + */ +public class DeviceFilter { + private static final String TAG = DeviceFilter.class.getSimpleName(); + + // USB Vendor ID (or -1 for unspecified) + public final int mVendorId; + // USB Product ID (or -1 for unspecified) + public final int mProductId; + // USB device or interface class (or -1 for unspecified) + public final int mClass; + // USB device subclass (or -1 for unspecified) + public final int mSubclass; + // USB device protocol (or -1 for unspecified) + public final int mProtocol; + // USB device manufacturer name string (or null for unspecified) + public final String mManufacturerName; + // USB device product name string (or null for unspecified) + public final String mProductName; + // USB device serial number string (or null for unspecified) + public final String mSerialNumber; + + public DeviceFilter(int vid, int pid, int clasz, int subclass, int protocol, + String manufacturer, String product, String serialnum) { + mVendorId = vid; + mProductId = pid; + mClass = clasz; + mSubclass = subclass; + mProtocol = protocol; + mManufacturerName = manufacturer; + mProductName = product; + mSerialNumber = serialnum; + } + + public DeviceFilter(UsbDevice device) { + mVendorId = device.getVendorId(); + mProductId = device.getProductId(); + mClass = device.getDeviceClass(); + mSubclass = device.getDeviceSubclass(); + mProtocol = device.getDeviceProtocol(); + mManufacturerName = device.getManufacturerName(); + mProductName = device.getProductName(); + mSerialNumber = device.getSerialNumber(); + } + + public static DeviceFilter read(XmlPullParser parser) + throws XmlPullParserException, IOException { + int vendorId = -1; + int productId = -1; + int deviceClass = -1; + int deviceSubclass = -1; + int deviceProtocol = -1; + String manufacturerName = null; + String productName = null; + String serialNumber = null; + + int count = parser.getAttributeCount(); + for (int i = 0; i < count; i++) { + String name = parser.getAttributeName(i); + String value = parser.getAttributeValue(i); + // Attribute values are ints or strings + if ("manufacturer-name".equals(name)) { + manufacturerName = value; + } else if ("product-name".equals(name)) { + productName = value; + } else if ("serial-number".equals(name)) { + serialNumber = value; + } else { + int intValue; + int radix = 10; + if (value != null && value.length() > 2 && value.charAt(0) == '0' && + (value.charAt(1) == 'x' || value.charAt(1) == 'X')) { + // allow hex values starting with 0x or 0X + radix = 16; + value = value.substring(2); + } + try { + intValue = Integer.parseInt(value, radix); + } catch (NumberFormatException e) { + Slog.e(TAG, "invalid number for field " + name, e); + continue; + } + if ("vendor-id".equals(name)) { + vendorId = intValue; + } else if ("product-id".equals(name)) { + productId = intValue; + } else if ("class".equals(name)) { + deviceClass = intValue; + } else if ("subclass".equals(name)) { + deviceSubclass = intValue; + } else if ("protocol".equals(name)) { + deviceProtocol = intValue; + } + } + } + return new DeviceFilter(vendorId, productId, + deviceClass, deviceSubclass, deviceProtocol, + manufacturerName, productName, serialNumber); + } + + public void write(XmlSerializer serializer) throws IOException { + serializer.startTag(null, "usb-device"); + if (mVendorId != -1) { + serializer.attribute(null, "vendor-id", Integer.toString(mVendorId)); + } + if (mProductId != -1) { + serializer.attribute(null, "product-id", Integer.toString(mProductId)); + } + if (mClass != -1) { + serializer.attribute(null, "class", Integer.toString(mClass)); + } + if (mSubclass != -1) { + serializer.attribute(null, "subclass", Integer.toString(mSubclass)); + } + if (mProtocol != -1) { + serializer.attribute(null, "protocol", Integer.toString(mProtocol)); + } + if (mManufacturerName != null) { + serializer.attribute(null, "manufacturer-name", mManufacturerName); + } + if (mProductName != null) { + serializer.attribute(null, "product-name", mProductName); + } + if (mSerialNumber != null) { + serializer.attribute(null, "serial-number", mSerialNumber); + } + serializer.endTag(null, "usb-device"); + } + + private boolean matches(int clasz, int subclass, int protocol) { + return ((mClass == -1 || clasz == mClass) && + (mSubclass == -1 || subclass == mSubclass) && + (mProtocol == -1 || protocol == mProtocol)); + } + + public boolean matches(UsbDevice device) { + if (mVendorId != -1 && device.getVendorId() != mVendorId) return false; + if (mProductId != -1 && device.getProductId() != mProductId) return false; + if (mManufacturerName != null && device.getManufacturerName() == null) return false; + if (mProductName != null && device.getProductName() == null) return false; + if (mSerialNumber != null && device.getSerialNumber() == null) return false; + if (mManufacturerName != null && device.getManufacturerName() != null && + !mManufacturerName.equals(device.getManufacturerName())) return false; + if (mProductName != null && device.getProductName() != null && + !mProductName.equals(device.getProductName())) return false; + if (mSerialNumber != null && device.getSerialNumber() != null && + !mSerialNumber.equals(device.getSerialNumber())) return false; + + // check device class/subclass/protocol + if (matches(device.getDeviceClass(), device.getDeviceSubclass(), + device.getDeviceProtocol())) return true; + + // if device doesn't match, check the interfaces + int count = device.getInterfaceCount(); + for (int i = 0; i < count; i++) { + UsbInterface intf = device.getInterface(i); + if (matches(intf.getInterfaceClass(), intf.getInterfaceSubclass(), + intf.getInterfaceProtocol())) return true; + } + + return false; + } + + /** + * If the device described by {@code device} covered by this filter? + * + * @param device The device + * + * @return {@code true} iff this filter covers the {@code device} + */ + public boolean contains(DeviceFilter device) { + // -1 and null means "match anything" + + if (mVendorId != -1 && device.mVendorId != mVendorId) return false; + if (mProductId != -1 && device.mProductId != mProductId) return false; + if (mManufacturerName != null && !Objects.equals(mManufacturerName, + device.mManufacturerName)) { + return false; + } + if (mProductName != null && !Objects.equals(mProductName, device.mProductName)) { + return false; + } + if (mSerialNumber != null + && !Objects.equals(mSerialNumber, device.mSerialNumber)) { + return false; + } + + // check device class/subclass/protocol + return matches(device.mClass, device.mSubclass, device.mProtocol); + } + + @Override + public boolean equals(Object obj) { + // can't compare if we have wildcard strings + if (mVendorId == -1 || mProductId == -1 || + mClass == -1 || mSubclass == -1 || mProtocol == -1) { + return false; + } + if (obj instanceof DeviceFilter) { + DeviceFilter filter = (DeviceFilter)obj; + + if (filter.mVendorId != mVendorId || + filter.mProductId != mProductId || + filter.mClass != mClass || + filter.mSubclass != mSubclass || + filter.mProtocol != mProtocol) { + return(false); + } + if ((filter.mManufacturerName != null && + mManufacturerName == null) || + (filter.mManufacturerName == null && + mManufacturerName != null) || + (filter.mProductName != null && + mProductName == null) || + (filter.mProductName == null && + mProductName != null) || + (filter.mSerialNumber != null && + mSerialNumber == null) || + (filter.mSerialNumber == null && + mSerialNumber != null)) { + return(false); + } + if ((filter.mManufacturerName != null && + mManufacturerName != null && + !mManufacturerName.equals(filter.mManufacturerName)) || + (filter.mProductName != null && + mProductName != null && + !mProductName.equals(filter.mProductName)) || + (filter.mSerialNumber != null && + mSerialNumber != null && + !mSerialNumber.equals(filter.mSerialNumber))) { + return false; + } + return true; + } + if (obj instanceof UsbDevice) { + UsbDevice device = (UsbDevice)obj; + if (device.getVendorId() != mVendorId || + device.getProductId() != mProductId || + device.getDeviceClass() != mClass || + device.getDeviceSubclass() != mSubclass || + device.getDeviceProtocol() != mProtocol) { + return(false); + } + if ((mManufacturerName != null && device.getManufacturerName() == null) || + (mManufacturerName == null && device.getManufacturerName() != null) || + (mProductName != null && device.getProductName() == null) || + (mProductName == null && device.getProductName() != null) || + (mSerialNumber != null && device.getSerialNumber() == null) || + (mSerialNumber == null && device.getSerialNumber() != null)) { + return(false); + } + if ((device.getManufacturerName() != null && + !mManufacturerName.equals(device.getManufacturerName())) || + (device.getProductName() != null && + !mProductName.equals(device.getProductName())) || + (device.getSerialNumber() != null && + !mSerialNumber.equals(device.getSerialNumber()))) { + return false; + } + return true; + } + return false; + } + + @Override + public int hashCode() { + return (((mVendorId << 16) | mProductId) ^ + ((mClass << 16) | (mSubclass << 8) | mProtocol)); + } + + @Override + public String toString() { + return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + mProductId + + ",mClass=" + mClass + ",mSubclass=" + mSubclass + + ",mProtocol=" + mProtocol + ",mManufacturerName=" + mManufacturerName + + ",mProductName=" + mProductName + ",mSerialNumber=" + mSerialNumber + + "]"; + } +} diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 1536b64dc41f1..b132160d45043 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -124,16 +124,16 @@ Physical keyboard - Allow the app %1$s to access the USB device? + Allow %1$s to access %2$s? - Allow the app %1$s to access the USB accessory? + Allow %1$s to access %2$s? - Open %1$s when this USB device is connected? + Open %1$s to handle %2$s? - Open %1$s when this USB accessory is connected? + Open %1$s to handle %2$s? No installed apps work with this USB accessory. Learn more about this accessory at %1$s @@ -145,10 +145,10 @@ View - Use by default for this USB device + Always open %1$s when %2$s is connected - - Use by default for this USB accessory + + Always open %1$s when %2$s is connected Allow USB debugging? diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java index 3eccccdd75bf6..e117969c5993f 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java @@ -71,10 +71,12 @@ public class UsbConfirmActivity extends AlertActivity ap.mIcon = mResolveInfo.loadIcon(packageManager); ap.mTitle = appName; if (mDevice == null) { - ap.mMessage = getString(R.string.usb_accessory_confirm_prompt, appName); + ap.mMessage = getString(R.string.usb_accessory_confirm_prompt, appName, + mAccessory.getDescription()); mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mAccessory); } else { - ap.mMessage = getString(R.string.usb_device_confirm_prompt, appName); + ap.mMessage = getString(R.string.usb_device_confirm_prompt, appName, + mDevice.getProductName()); mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mDevice); } ap.mPositiveButtonText = getString(android.R.string.ok); @@ -88,9 +90,11 @@ public class UsbConfirmActivity extends AlertActivity ap.mView = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null); mAlwaysUse = (CheckBox)ap.mView.findViewById(com.android.internal.R.id.alwaysUse); if (mDevice == null) { - mAlwaysUse.setText(R.string.always_use_accessory); + mAlwaysUse.setText(getString(R.string.always_use_accessory, appName, + mAccessory.getDescription())); } else { - mAlwaysUse.setText(R.string.always_use_device); + mAlwaysUse.setText(getString(R.string.always_use_device, appName, + mDevice.getProductName())); } mAlwaysUse.setOnCheckedChangeListener(this); mClearDefaultHint = (TextView)ap.mView.findViewById( diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java index 1e69fc5ce1fcb..87d11b2408b12 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java @@ -16,13 +16,17 @@ package com.android.systemui.usb; +import android.annotation.NonNull; import android.app.AlertDialog; import android.app.PendingIntent; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.res.XmlResourceParser; import android.hardware.usb.IUsbManager; import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbDevice; @@ -41,8 +45,13 @@ import android.widget.TextView; import com.android.internal.app.AlertActivity; import com.android.internal.app.AlertController; +import com.android.internal.util.XmlUtils; +import android.hardware.usb.AccessoryFilter; +import android.hardware.usb.DeviceFilter; import com.android.systemui.R; +import org.xmlpull.v1.XmlPullParser; + public class UsbPermissionActivity extends AlertActivity implements DialogInterface.OnClickListener, CheckBox.OnCheckedChangeListener { @@ -84,10 +93,12 @@ public class UsbPermissionActivity extends AlertActivity ap.mIcon = aInfo.loadIcon(packageManager); ap.mTitle = appName; if (mDevice == null) { - ap.mMessage = getString(R.string.usb_accessory_permission_prompt, appName); + ap.mMessage = getString(R.string.usb_accessory_permission_prompt, appName, + mAccessory.getDescription()); mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mAccessory); } else { - ap.mMessage = getString(R.string.usb_device_permission_prompt, appName); + ap.mMessage = getString(R.string.usb_device_permission_prompt, appName, + mDevice.getProductName()); mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mDevice); } ap.mPositiveButtonText = getString(android.R.string.ok); @@ -95,25 +106,123 @@ public class UsbPermissionActivity extends AlertActivity ap.mPositiveButtonListener = this; ap.mNegativeButtonListener = this; - // add "always use" checkbox - LayoutInflater inflater = (LayoutInflater)getSystemService( - Context.LAYOUT_INFLATER_SERVICE); - ap.mView = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null); - mAlwaysUse = (CheckBox)ap.mView.findViewById(com.android.internal.R.id.alwaysUse); - if (mDevice == null) { - mAlwaysUse.setText(R.string.always_use_accessory); - } else { - mAlwaysUse.setText(R.string.always_use_device); + try { + PackageInfo packageInfo = packageManager.getPackageInfo(mPackageName, + PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA); + + if ((mDevice != null && canBeDefault(mDevice, packageInfo)) + || (mAccessory != null && canBeDefault(mAccessory, packageInfo))) { + // add "open when" checkbox + LayoutInflater inflater = (LayoutInflater) getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + ap.mView = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null); + mAlwaysUse = (CheckBox) ap.mView.findViewById(com.android.internal.R.id.alwaysUse); + if (mDevice == null) { + mAlwaysUse.setText(getString(R.string.always_use_accessory, appName, + mAccessory.getDescription())); + } else { + mAlwaysUse.setText(getString(R.string.always_use_device, appName, + mDevice.getProductName())); + } + mAlwaysUse.setOnCheckedChangeListener(this); + + mClearDefaultHint = (TextView)ap.mView.findViewById( + com.android.internal.R.id.clearDefaultHint); + mClearDefaultHint.setVisibility(View.GONE); + } + } catch (PackageManager.NameNotFoundException e) { + // ignore } - mAlwaysUse.setOnCheckedChangeListener(this); - mClearDefaultHint = (TextView)ap.mView.findViewById( - com.android.internal.R.id.clearDefaultHint); - mClearDefaultHint.setVisibility(View.GONE); setupAlert(); } + /** + * Can the app be the default for the USB device. I.e. can the app be launched by default if + * the device is plugged in. + * + * @param device The device the app would be default for + * @param packageInfo The package info of the app + * + * @return {@code true} iff the app can be default + */ + private boolean canBeDefault(@NonNull UsbDevice device, @NonNull PackageInfo packageInfo) { + ActivityInfo[] activities = packageInfo.activities; + if (activities != null) { + int numActivities = activities.length; + for (int i = 0; i < numActivities; i++) { + ActivityInfo activityInfo = activities[i]; + + try (XmlResourceParser parser = activityInfo.loadXmlMetaData(getPackageManager(), + UsbManager.ACTION_USB_DEVICE_ATTACHED)) { + if (parser == null) { + continue; + } + + XmlUtils.nextElement(parser); + while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { + if ("usb-device".equals(parser.getName())) { + DeviceFilter filter = DeviceFilter.read(parser); + if (filter.matches(device)) { + return true; + } + } + + XmlUtils.nextElement(parser); + } + } catch (Exception e) { + Log.w(TAG, "Unable to load component info " + activityInfo.toString(), e); + } + } + } + + return false; + } + + /** + * Can the app be the default for the USB accessory. I.e. can the app be launched by default if + * the accessory is plugged in. + * + * @param accessory The accessory the app would be default for + * @param packageInfo The package info of the app + * + * @return {@code true} iff the app can be default + */ + private boolean canBeDefault(@NonNull UsbAccessory accessory, + @NonNull PackageInfo packageInfo) { + ActivityInfo[] activities = packageInfo.activities; + if (activities != null) { + int numActivities = activities.length; + for (int i = 0; i < numActivities; i++) { + ActivityInfo activityInfo = activities[i]; + + try (XmlResourceParser parser = activityInfo.loadXmlMetaData(getPackageManager(), + UsbManager.ACTION_USB_ACCESSORY_ATTACHED)) { + if (parser == null) { + continue; + } + + XmlUtils.nextElement(parser); + while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { + if ("usb-accessory".equals(parser.getName())) { + AccessoryFilter filter = AccessoryFilter.read(parser); + if (filter.matches(accessory)) { + return true; + } + } + + XmlUtils.nextElement(parser); + } + } catch (Exception e) { + Log.w(TAG, "Unable to load component info " + activityInfo.toString(), e); + } + } + } + + return false; + } + @Override public void onDestroy() { IBinder b = ServiceManager.getService(USB_SERVICE); diff --git a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java index ebb5a62ce7ece..917e651bb4255 100644 --- a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java +++ b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java @@ -32,9 +32,10 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; import android.content.res.XmlResourceParser; +import android.hardware.usb.AccessoryFilter; +import android.hardware.usb.DeviceFilter; import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbManager; import android.os.AsyncTask; import android.os.Environment; @@ -58,7 +59,6 @@ import libcore.io.IoUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlSerializer; import java.io.File; import java.io.FileInputStream; @@ -71,7 +71,6 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Objects; class UsbProfileGroupSettingsManager { private static final String TAG = UsbProfileGroupSettingsManager.class.getSimpleName(); @@ -157,404 +156,6 @@ class UsbProfileGroupSettingsManager { } } - // This class is used to describe a USB device. - // When used in HashMaps all values must be specified, - // but wildcards can be used for any of the fields in - // the package meta-data. - private static class DeviceFilter { - // USB Vendor ID (or -1 for unspecified) - public final int mVendorId; - // USB Product ID (or -1 for unspecified) - public final int mProductId; - // USB device or interface class (or -1 for unspecified) - public final int mClass; - // USB device subclass (or -1 for unspecified) - public final int mSubclass; - // USB device protocol (or -1 for unspecified) - public final int mProtocol; - // USB device manufacturer name string (or null for unspecified) - public final String mManufacturerName; - // USB device product name string (or null for unspecified) - public final String mProductName; - // USB device serial number string (or null for unspecified) - public final String mSerialNumber; - - public DeviceFilter(int vid, int pid, int clasz, int subclass, int protocol, - String manufacturer, String product, String serialnum) { - mVendorId = vid; - mProductId = pid; - mClass = clasz; - mSubclass = subclass; - mProtocol = protocol; - mManufacturerName = manufacturer; - mProductName = product; - mSerialNumber = serialnum; - } - - public DeviceFilter(UsbDevice device) { - mVendorId = device.getVendorId(); - mProductId = device.getProductId(); - mClass = device.getDeviceClass(); - mSubclass = device.getDeviceSubclass(); - mProtocol = device.getDeviceProtocol(); - mManufacturerName = device.getManufacturerName(); - mProductName = device.getProductName(); - mSerialNumber = device.getSerialNumber(); - } - - public static DeviceFilter read(XmlPullParser parser) - throws XmlPullParserException, IOException { - int vendorId = -1; - int productId = -1; - int deviceClass = -1; - int deviceSubclass = -1; - int deviceProtocol = -1; - String manufacturerName = null; - String productName = null; - String serialNumber = null; - - int count = parser.getAttributeCount(); - for (int i = 0; i < count; i++) { - String name = parser.getAttributeName(i); - String value = parser.getAttributeValue(i); - // Attribute values are ints or strings - if ("manufacturer-name".equals(name)) { - manufacturerName = value; - } else if ("product-name".equals(name)) { - productName = value; - } else if ("serial-number".equals(name)) { - serialNumber = value; - } else { - int intValue; - int radix = 10; - if (value != null && value.length() > 2 && value.charAt(0) == '0' && - (value.charAt(1) == 'x' || value.charAt(1) == 'X')) { - // allow hex values starting with 0x or 0X - radix = 16; - value = value.substring(2); - } - try { - intValue = Integer.parseInt(value, radix); - } catch (NumberFormatException e) { - Slog.e(TAG, "invalid number for field " + name, e); - continue; - } - if ("vendor-id".equals(name)) { - vendorId = intValue; - } else if ("product-id".equals(name)) { - productId = intValue; - } else if ("class".equals(name)) { - deviceClass = intValue; - } else if ("subclass".equals(name)) { - deviceSubclass = intValue; - } else if ("protocol".equals(name)) { - deviceProtocol = intValue; - } - } - } - return new DeviceFilter(vendorId, productId, - deviceClass, deviceSubclass, deviceProtocol, - manufacturerName, productName, serialNumber); - } - - public void write(XmlSerializer serializer) throws IOException { - serializer.startTag(null, "usb-device"); - if (mVendorId != -1) { - serializer.attribute(null, "vendor-id", Integer.toString(mVendorId)); - } - if (mProductId != -1) { - serializer.attribute(null, "product-id", Integer.toString(mProductId)); - } - if (mClass != -1) { - serializer.attribute(null, "class", Integer.toString(mClass)); - } - if (mSubclass != -1) { - serializer.attribute(null, "subclass", Integer.toString(mSubclass)); - } - if (mProtocol != -1) { - serializer.attribute(null, "protocol", Integer.toString(mProtocol)); - } - if (mManufacturerName != null) { - serializer.attribute(null, "manufacturer-name", mManufacturerName); - } - if (mProductName != null) { - serializer.attribute(null, "product-name", mProductName); - } - if (mSerialNumber != null) { - serializer.attribute(null, "serial-number", mSerialNumber); - } - serializer.endTag(null, "usb-device"); - } - - private boolean matches(int clasz, int subclass, int protocol) { - return ((mClass == -1 || clasz == mClass) && - (mSubclass == -1 || subclass == mSubclass) && - (mProtocol == -1 || protocol == mProtocol)); - } - - public boolean matches(UsbDevice device) { - if (mVendorId != -1 && device.getVendorId() != mVendorId) return false; - if (mProductId != -1 && device.getProductId() != mProductId) return false; - if (mManufacturerName != null && device.getManufacturerName() == null) return false; - if (mProductName != null && device.getProductName() == null) return false; - if (mSerialNumber != null && device.getSerialNumber() == null) return false; - if (mManufacturerName != null && device.getManufacturerName() != null && - !mManufacturerName.equals(device.getManufacturerName())) return false; - if (mProductName != null && device.getProductName() != null && - !mProductName.equals(device.getProductName())) return false; - if (mSerialNumber != null && device.getSerialNumber() != null && - !mSerialNumber.equals(device.getSerialNumber())) return false; - - // check device class/subclass/protocol - if (matches(device.getDeviceClass(), device.getDeviceSubclass(), - device.getDeviceProtocol())) return true; - - // if device doesn't match, check the interfaces - int count = device.getInterfaceCount(); - for (int i = 0; i < count; i++) { - UsbInterface intf = device.getInterface(i); - if (matches(intf.getInterfaceClass(), intf.getInterfaceSubclass(), - intf.getInterfaceProtocol())) return true; - } - - return false; - } - - /** - * If the device described by {@code device} covered by this filter? - * - * @param device The device - * - * @return {@code true} iff this filter covers the {@code device} - */ - public boolean contains(DeviceFilter device) { - // -1 and null means "match anything" - - if (mVendorId != -1 && device.mVendorId != mVendorId) return false; - if (mProductId != -1 && device.mProductId != mProductId) return false; - if (mManufacturerName != null && !Objects.equals(mManufacturerName, - device.mManufacturerName)) { - return false; - } - if (mProductName != null && !Objects.equals(mProductName, device.mProductName)) { - return false; - } - if (mSerialNumber != null - && !Objects.equals(mSerialNumber, device.mSerialNumber)) { - return false; - } - - // check device class/subclass/protocol - return matches(device.mClass, device.mSubclass, device.mProtocol); - } - - @Override - public boolean equals(Object obj) { - // can't compare if we have wildcard strings - if (mVendorId == -1 || mProductId == -1 || - mClass == -1 || mSubclass == -1 || mProtocol == -1) { - return false; - } - if (obj instanceof DeviceFilter) { - DeviceFilter filter = (DeviceFilter)obj; - - if (filter.mVendorId != mVendorId || - filter.mProductId != mProductId || - filter.mClass != mClass || - filter.mSubclass != mSubclass || - filter.mProtocol != mProtocol) { - return(false); - } - if ((filter.mManufacturerName != null && - mManufacturerName == null) || - (filter.mManufacturerName == null && - mManufacturerName != null) || - (filter.mProductName != null && - mProductName == null) || - (filter.mProductName == null && - mProductName != null) || - (filter.mSerialNumber != null && - mSerialNumber == null) || - (filter.mSerialNumber == null && - mSerialNumber != null)) { - return(false); - } - if ((filter.mManufacturerName != null && - mManufacturerName != null && - !mManufacturerName.equals(filter.mManufacturerName)) || - (filter.mProductName != null && - mProductName != null && - !mProductName.equals(filter.mProductName)) || - (filter.mSerialNumber != null && - mSerialNumber != null && - !mSerialNumber.equals(filter.mSerialNumber))) { - return false; - } - return true; - } - if (obj instanceof UsbDevice) { - UsbDevice device = (UsbDevice)obj; - if (device.getVendorId() != mVendorId || - device.getProductId() != mProductId || - device.getDeviceClass() != mClass || - device.getDeviceSubclass() != mSubclass || - device.getDeviceProtocol() != mProtocol) { - return(false); - } - if ((mManufacturerName != null && device.getManufacturerName() == null) || - (mManufacturerName == null && device.getManufacturerName() != null) || - (mProductName != null && device.getProductName() == null) || - (mProductName == null && device.getProductName() != null) || - (mSerialNumber != null && device.getSerialNumber() == null) || - (mSerialNumber == null && device.getSerialNumber() != null)) { - return(false); - } - if ((device.getManufacturerName() != null && - !mManufacturerName.equals(device.getManufacturerName())) || - (device.getProductName() != null && - !mProductName.equals(device.getProductName())) || - (device.getSerialNumber() != null && - !mSerialNumber.equals(device.getSerialNumber()))) { - return false; - } - return true; - } - return false; - } - - @Override - public int hashCode() { - return (((mVendorId << 16) | mProductId) ^ - ((mClass << 16) | (mSubclass << 8) | mProtocol)); - } - - @Override - public String toString() { - return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + mProductId + - ",mClass=" + mClass + ",mSubclass=" + mSubclass + - ",mProtocol=" + mProtocol + ",mManufacturerName=" + mManufacturerName + - ",mProductName=" + mProductName + ",mSerialNumber=" + mSerialNumber + - "]"; - } - } - - // This class is used to describe a USB accessory. - // When used in HashMaps all values must be specified, - // but wildcards can be used for any of the fields in - // the package meta-data. - private static class AccessoryFilter { - // USB accessory manufacturer (or null for unspecified) - public final String mManufacturer; - // USB accessory model (or null for unspecified) - public final String mModel; - // USB accessory version (or null for unspecified) - public final String mVersion; - - public AccessoryFilter(String manufacturer, String model, String version) { - mManufacturer = manufacturer; - mModel = model; - mVersion = version; - } - - public AccessoryFilter(UsbAccessory accessory) { - mManufacturer = accessory.getManufacturer(); - mModel = accessory.getModel(); - mVersion = accessory.getVersion(); - } - - public static AccessoryFilter read(XmlPullParser parser) - throws XmlPullParserException, IOException { - String manufacturer = null; - String model = null; - String version = null; - - int count = parser.getAttributeCount(); - for (int i = 0; i < count; i++) { - String name = parser.getAttributeName(i); - String value = parser.getAttributeValue(i); - - if ("manufacturer".equals(name)) { - manufacturer = value; - } else if ("model".equals(name)) { - model = value; - } else if ("version".equals(name)) { - version = value; - } - } - return new AccessoryFilter(manufacturer, model, version); - } - - public void write(XmlSerializer serializer)throws IOException { - serializer.startTag(null, "usb-accessory"); - if (mManufacturer != null) { - serializer.attribute(null, "manufacturer", mManufacturer); - } - if (mModel != null) { - serializer.attribute(null, "model", mModel); - } - if (mVersion != null) { - serializer.attribute(null, "version", mVersion); - } - serializer.endTag(null, "usb-accessory"); - } - - public boolean matches(UsbAccessory acc) { - if (mManufacturer != null && !acc.getManufacturer().equals(mManufacturer)) return false; - if (mModel != null && !acc.getModel().equals(mModel)) return false; - return !(mVersion != null && !acc.getVersion().equals(mVersion)); - } - - /** - * Is the accessories described {@code accessory} covered by this filter? - * - * @param accessory A filter describing the accessory - * - * @return {@code true} iff this the filter covers the accessory - */ - public boolean contains(AccessoryFilter accessory) { - if (mManufacturer != null && !Objects.equals(accessory.mManufacturer, mManufacturer)) { - return false; - } - if (mModel != null && !Objects.equals(accessory.mModel, mModel)) return false; - return !(mVersion != null && !Objects.equals(accessory.mVersion, mVersion)); - } - - @Override - public boolean equals(Object obj) { - // can't compare if we have wildcard strings - if (mManufacturer == null || mModel == null || mVersion == null) { - return false; - } - if (obj instanceof AccessoryFilter) { - AccessoryFilter filter = (AccessoryFilter)obj; - return (mManufacturer.equals(filter.mManufacturer) && - mModel.equals(filter.mModel) && - mVersion.equals(filter.mVersion)); - } - if (obj instanceof UsbAccessory) { - UsbAccessory accessory = (UsbAccessory)obj; - return (mManufacturer.equals(accessory.getManufacturer()) && - mModel.equals(accessory.getModel()) && - mVersion.equals(accessory.getVersion())); - } - return false; - } - - @Override - public int hashCode() { - return ((mManufacturer == null ? 0 : mManufacturer.hashCode()) ^ - (mModel == null ? 0 : mModel.hashCode()) ^ - (mVersion == null ? 0 : mVersion.hashCode())); - } - - @Override - public String toString() { - return "AccessoryFilter[mManufacturer=\"" + mManufacturer + - "\", mModel=\"" + mModel + - "\", mVersion=\"" + mVersion + "\"]"; - } - } - private class MyPackageMonitor extends PackageMonitor { @Override public void onPackageAdded(String packageName, int uid) {