| /* |
| * Copyright (C) 2011 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.server.usb; |
| |
| import android.app.PendingIntent; |
| import android.content.ActivityNotFoundException; |
| import android.content.ComponentName; |
| import android.content.Context; |
| 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.pm.PackageManager.NameNotFoundException; |
| import android.content.pm.ResolveInfo; |
| import android.content.res.XmlResourceParser; |
| import android.hardware.usb.UsbAccessory; |
| import android.hardware.usb.UsbDevice; |
| import android.hardware.usb.UsbInterface; |
| import android.hardware.usb.UsbManager; |
| import android.os.Binder; |
| import android.os.Environment; |
| import android.os.Process; |
| import android.os.UserHandle; |
| import android.util.AtomicFile; |
| import android.util.Log; |
| import android.util.Slog; |
| import android.util.SparseBooleanArray; |
| import android.util.Xml; |
| |
| import com.android.internal.content.PackageMonitor; |
| import com.android.internal.util.FastXmlSerializer; |
| import com.android.internal.util.IndentingPrintWriter; |
| import com.android.internal.util.XmlUtils; |
| |
| import org.xmlpull.v1.XmlPullParser; |
| import org.xmlpull.v1.XmlPullParserException; |
| import org.xmlpull.v1.XmlSerializer; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.nio.charset.StandardCharsets; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| |
| import libcore.io.IoUtils; |
| |
| class UsbSettingsManager { |
| private static final String TAG = "UsbSettingsManager"; |
| private static final boolean DEBUG = false; |
| |
| /** Legacy settings file, before multi-user */ |
| private static final File sSingleUserSettingsFile = new File( |
| "/data/system/usb_device_manager.xml"); |
| |
| private final UserHandle mUser; |
| private final AtomicFile mSettingsFile; |
| private final boolean mDisablePermissionDialogs; |
| |
| private final Context mContext; |
| private final Context mUserContext; |
| private final PackageManager mPackageManager; |
| |
| // Temporary mapping USB device name to list of UIDs with permissions for the device |
| private final HashMap<String, SparseBooleanArray> mDevicePermissionMap = |
| new HashMap<String, SparseBooleanArray>(); |
| // Temporary mapping UsbAccessory to list of UIDs with permissions for the accessory |
| private final HashMap<UsbAccessory, SparseBooleanArray> mAccessoryPermissionMap = |
| new HashMap<UsbAccessory, SparseBooleanArray>(); |
| // Maps DeviceFilter to user preferred application package |
| private final HashMap<DeviceFilter, String> mDevicePreferenceMap = |
| new HashMap<DeviceFilter, String>(); |
| // Maps AccessoryFilter to user preferred application package |
| private final HashMap<AccessoryFilter, String> mAccessoryPreferenceMap = |
| new HashMap<AccessoryFilter, String>(); |
| |
| private final Object mLock = new Object(); |
| |
| // 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 = -1; |
| 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; |
| } |
| |
| public boolean matches(DeviceFilter f) { |
| if (mVendorId != -1 && f.mVendorId != mVendorId) return false; |
| if (mProductId != -1 && f.mProductId != mProductId) return false; |
| if (f.mManufacturerName != null && mManufacturerName == null) return false; |
| if (f.mProductName != null && mProductName == null) return false; |
| if (f.mSerialNumber != null && mSerialNumber == null) return false; |
| if (mManufacturerName != null && f.mManufacturerName != null && |
| !mManufacturerName.equals(f.mManufacturerName)) return false; |
| if (mProductName != null && f.mProductName != null && |
| !mProductName.equals(f.mProductName)) return false; |
| if (mSerialNumber != null && f.mSerialNumber != null && |
| !mSerialNumber.equals(f.mSerialNumber)) return false; |
| |
| // check device class/subclass/protocol |
| return matches(f.mClass, f.mSubclass, f.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; |
| if (mVersion != null && !acc.getVersion().equals(mVersion)) return false; |
| return true; |
| } |
| |
| public boolean matches(AccessoryFilter f) { |
| if (mManufacturer != null && !f.mManufacturer.equals(mManufacturer)) return false; |
| if (mModel != null && !f.mModel.equals(mModel)) return false; |
| if (mVersion != null && !f.mVersion.equals(mVersion)) return false; |
| return true; |
| } |
| |
| @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) { |
| handlePackageUpdate(packageName); |
| } |
| |
| @Override |
| public boolean onPackageChanged(String packageName, int uid, String[] components) { |
| handlePackageUpdate(packageName); |
| return false; |
| } |
| |
| @Override |
| public void onPackageRemoved(String packageName, int uid) { |
| clearDefaults(packageName); |
| } |
| } |
| |
| MyPackageMonitor mPackageMonitor = new MyPackageMonitor(); |
| |
| private final MtpNotificationManager mMtpNotificationManager; |
| |
| public UsbSettingsManager(Context context, UserHandle user) { |
| if (DEBUG) Slog.v(TAG, "Creating settings for " + user); |
| |
| try { |
| mUserContext = context.createPackageContextAsUser("android", 0, user); |
| } catch (NameNotFoundException e) { |
| throw new RuntimeException("Missing android package"); |
| } |
| |
| mContext = context; |
| mPackageManager = mUserContext.getPackageManager(); |
| |
| mUser = user; |
| mSettingsFile = new AtomicFile(new File( |
| Environment.getUserSystemDirectory(user.getIdentifier()), |
| "usb_device_manager.xml")); |
| |
| mDisablePermissionDialogs = context.getResources().getBoolean( |
| com.android.internal.R.bool.config_disableUsbPermissionDialogs); |
| |
| synchronized (mLock) { |
| if (UserHandle.SYSTEM.equals(user)) { |
| upgradeSingleUserLocked(); |
| } |
| readSettingsLocked(); |
| } |
| |
| mPackageMonitor.register(mUserContext, null, true); |
| mMtpNotificationManager = new MtpNotificationManager( |
| context, |
| new MtpNotificationManager.OnOpenInAppListener() { |
| @Override |
| public void onOpenInApp(UsbDevice device) { |
| resolveActivity(createDeviceAttachedIntent(device), device); |
| } |
| }); |
| } |
| |
| private void readPreference(XmlPullParser parser) |
| throws XmlPullParserException, IOException { |
| String packageName = null; |
| int count = parser.getAttributeCount(); |
| for (int i = 0; i < count; i++) { |
| if ("package".equals(parser.getAttributeName(i))) { |
| packageName = parser.getAttributeValue(i); |
| break; |
| } |
| } |
| XmlUtils.nextElement(parser); |
| if ("usb-device".equals(parser.getName())) { |
| DeviceFilter filter = DeviceFilter.read(parser); |
| mDevicePreferenceMap.put(filter, packageName); |
| } else if ("usb-accessory".equals(parser.getName())) { |
| AccessoryFilter filter = AccessoryFilter.read(parser); |
| mAccessoryPreferenceMap.put(filter, packageName); |
| } |
| XmlUtils.nextElement(parser); |
| } |
| |
| /** |
| * Upgrade any single-user settings from {@link #sSingleUserSettingsFile}. |
| * Should only by called by owner. |
| */ |
| private void upgradeSingleUserLocked() { |
| if (sSingleUserSettingsFile.exists()) { |
| mDevicePreferenceMap.clear(); |
| mAccessoryPreferenceMap.clear(); |
| |
| FileInputStream fis = null; |
| try { |
| fis = new FileInputStream(sSingleUserSettingsFile); |
| XmlPullParser parser = Xml.newPullParser(); |
| parser.setInput(fis, StandardCharsets.UTF_8.name()); |
| |
| XmlUtils.nextElement(parser); |
| while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { |
| final String tagName = parser.getName(); |
| if ("preference".equals(tagName)) { |
| readPreference(parser); |
| } else { |
| XmlUtils.nextElement(parser); |
| } |
| } |
| } catch (IOException e) { |
| Log.wtf(TAG, "Failed to read single-user settings", e); |
| } catch (XmlPullParserException e) { |
| Log.wtf(TAG, "Failed to read single-user settings", e); |
| } finally { |
| IoUtils.closeQuietly(fis); |
| } |
| |
| writeSettingsLocked(); |
| |
| // Success or failure, we delete single-user file |
| sSingleUserSettingsFile.delete(); |
| } |
| } |
| |
| private void readSettingsLocked() { |
| if (DEBUG) Slog.v(TAG, "readSettingsLocked()"); |
| |
| mDevicePreferenceMap.clear(); |
| mAccessoryPreferenceMap.clear(); |
| |
| FileInputStream stream = null; |
| try { |
| stream = mSettingsFile.openRead(); |
| XmlPullParser parser = Xml.newPullParser(); |
| parser.setInput(stream, StandardCharsets.UTF_8.name()); |
| |
| XmlUtils.nextElement(parser); |
| while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { |
| String tagName = parser.getName(); |
| if ("preference".equals(tagName)) { |
| readPreference(parser); |
| } else { |
| XmlUtils.nextElement(parser); |
| } |
| } |
| } catch (FileNotFoundException e) { |
| if (DEBUG) Slog.d(TAG, "settings file not found"); |
| } catch (Exception e) { |
| Slog.e(TAG, "error reading settings file, deleting to start fresh", e); |
| mSettingsFile.delete(); |
| } finally { |
| IoUtils.closeQuietly(stream); |
| } |
| } |
| |
| private void writeSettingsLocked() { |
| if (DEBUG) Slog.v(TAG, "writeSettingsLocked()"); |
| |
| FileOutputStream fos = null; |
| try { |
| fos = mSettingsFile.startWrite(); |
| |
| FastXmlSerializer serializer = new FastXmlSerializer(); |
| serializer.setOutput(fos, StandardCharsets.UTF_8.name()); |
| serializer.startDocument(null, true); |
| serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); |
| serializer.startTag(null, "settings"); |
| |
| for (DeviceFilter filter : mDevicePreferenceMap.keySet()) { |
| serializer.startTag(null, "preference"); |
| serializer.attribute(null, "package", mDevicePreferenceMap.get(filter)); |
| filter.write(serializer); |
| serializer.endTag(null, "preference"); |
| } |
| |
| for (AccessoryFilter filter : mAccessoryPreferenceMap.keySet()) { |
| serializer.startTag(null, "preference"); |
| serializer.attribute(null, "package", mAccessoryPreferenceMap.get(filter)); |
| filter.write(serializer); |
| serializer.endTag(null, "preference"); |
| } |
| |
| serializer.endTag(null, "settings"); |
| serializer.endDocument(); |
| |
| mSettingsFile.finishWrite(fos); |
| } catch (IOException e) { |
| Slog.e(TAG, "Failed to write settings", e); |
| if (fos != null) { |
| mSettingsFile.failWrite(fos); |
| } |
| } |
| } |
| |
| // Checks to see if a package matches a device or accessory. |
| // Only one of device and accessory should be non-null. |
| private boolean packageMatchesLocked(ResolveInfo info, String metaDataName, |
| UsbDevice device, UsbAccessory accessory) { |
| ActivityInfo ai = info.activityInfo; |
| |
| XmlResourceParser parser = null; |
| try { |
| parser = ai.loadXmlMetaData(mPackageManager, metaDataName); |
| if (parser == null) { |
| Slog.w(TAG, "no meta-data for " + info); |
| return false; |
| } |
| |
| XmlUtils.nextElement(parser); |
| while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { |
| String tagName = parser.getName(); |
| if (device != null && "usb-device".equals(tagName)) { |
| DeviceFilter filter = DeviceFilter.read(parser); |
| if (filter.matches(device)) { |
| return true; |
| } |
| } |
| else if (accessory != null && "usb-accessory".equals(tagName)) { |
| AccessoryFilter filter = AccessoryFilter.read(parser); |
| if (filter.matches(accessory)) { |
| return true; |
| } |
| } |
| XmlUtils.nextElement(parser); |
| } |
| } catch (Exception e) { |
| Slog.w(TAG, "Unable to load component info " + info.toString(), e); |
| } finally { |
| if (parser != null) parser.close(); |
| } |
| return false; |
| } |
| |
| private final ArrayList<ResolveInfo> getDeviceMatchesLocked(UsbDevice device, Intent intent) { |
| ArrayList<ResolveInfo> matches = new ArrayList<ResolveInfo>(); |
| List<ResolveInfo> resolveInfos = mPackageManager.queryIntentActivities(intent, |
| PackageManager.GET_META_DATA); |
| int count = resolveInfos.size(); |
| for (int i = 0; i < count; i++) { |
| ResolveInfo resolveInfo = resolveInfos.get(i); |
| if (packageMatchesLocked(resolveInfo, intent.getAction(), device, null)) { |
| matches.add(resolveInfo); |
| } |
| } |
| return matches; |
| } |
| |
| private final ArrayList<ResolveInfo> getAccessoryMatchesLocked( |
| UsbAccessory accessory, Intent intent) { |
| ArrayList<ResolveInfo> matches = new ArrayList<ResolveInfo>(); |
| List<ResolveInfo> resolveInfos = mPackageManager.queryIntentActivities(intent, |
| PackageManager.GET_META_DATA); |
| int count = resolveInfos.size(); |
| for (int i = 0; i < count; i++) { |
| ResolveInfo resolveInfo = resolveInfos.get(i); |
| if (packageMatchesLocked(resolveInfo, intent.getAction(), null, accessory)) { |
| matches.add(resolveInfo); |
| } |
| } |
| return matches; |
| } |
| |
| public void deviceAttached(UsbDevice device) { |
| final Intent intent = createDeviceAttachedIntent(device); |
| |
| // Send broadcast to running activity with registered intent |
| mUserContext.sendBroadcast(intent); |
| |
| if (MtpNotificationManager.shouldShowNotification(mPackageManager, device)) { |
| // Show notification if the device is MTP storage. |
| mMtpNotificationManager.showNotification(device); |
| } else { |
| resolveActivity(intent, device); |
| } |
| } |
| |
| private void resolveActivity(Intent intent, UsbDevice device) { |
| ArrayList<ResolveInfo> matches; |
| String defaultPackage; |
| synchronized (mLock) { |
| matches = getDeviceMatchesLocked(device, intent); |
| // Launch our default activity directly, if we have one. |
| // Otherwise we will start the UsbResolverActivity to allow the user to choose. |
| defaultPackage = mDevicePreferenceMap.get(new DeviceFilter(device)); |
| } |
| |
| // Start activity with registered intent |
| resolveActivity(intent, matches, defaultPackage, device, null); |
| } |
| |
| public void deviceDetached(UsbDevice device) { |
| // clear temporary permissions for the device |
| mDevicePermissionMap.remove(device.getDeviceName()); |
| |
| Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_DETACHED); |
| intent.putExtra(UsbManager.EXTRA_DEVICE, device); |
| if (DEBUG) Slog.d(TAG, "usbDeviceRemoved, sending " + intent); |
| mContext.sendBroadcastAsUser(intent, UserHandle.ALL); |
| |
| mMtpNotificationManager.hideNotification(device.getDeviceId()); |
| } |
| |
| public void accessoryAttached(UsbAccessory accessory) { |
| Intent intent = new Intent(UsbManager.ACTION_USB_ACCESSORY_ATTACHED); |
| intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); |
| intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| |
| ArrayList<ResolveInfo> matches; |
| String defaultPackage; |
| synchronized (mLock) { |
| matches = getAccessoryMatchesLocked(accessory, intent); |
| // Launch our default activity directly, if we have one. |
| // Otherwise we will start the UsbResolverActivity to allow the user to choose. |
| defaultPackage = mAccessoryPreferenceMap.get(new AccessoryFilter(accessory)); |
| } |
| |
| resolveActivity(intent, matches, defaultPackage, null, accessory); |
| } |
| |
| public void accessoryDetached(UsbAccessory accessory) { |
| // clear temporary permissions for the accessory |
| mAccessoryPermissionMap.remove(accessory); |
| |
| Intent intent = new Intent( |
| UsbManager.ACTION_USB_ACCESSORY_DETACHED); |
| intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); |
| mContext.sendBroadcastAsUser(intent, UserHandle.ALL); |
| } |
| |
| private void resolveActivity(Intent intent, ArrayList<ResolveInfo> matches, |
| String defaultPackage, UsbDevice device, UsbAccessory accessory) { |
| int count = matches.size(); |
| |
| // don't show the resolver activity if there are no choices available |
| if (count == 0) { |
| if (accessory != null) { |
| String uri = accessory.getUri(); |
| if (uri != null && uri.length() > 0) { |
| // display URI to user |
| // start UsbResolverActivity so user can choose an activity |
| Intent dialogIntent = new Intent(); |
| dialogIntent.setClassName("com.android.systemui", |
| "com.android.systemui.usb.UsbAccessoryUriActivity"); |
| dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| dialogIntent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); |
| dialogIntent.putExtra("uri", uri); |
| try { |
| mUserContext.startActivityAsUser(dialogIntent, mUser); |
| } catch (ActivityNotFoundException e) { |
| Slog.e(TAG, "unable to start UsbAccessoryUriActivity"); |
| } |
| } |
| } |
| |
| // do nothing |
| return; |
| } |
| |
| ResolveInfo defaultRI = null; |
| if (count == 1 && defaultPackage == null) { |
| // Check to see if our single choice is on the system partition. |
| // If so, treat it as our default without calling UsbResolverActivity |
| ResolveInfo rInfo = matches.get(0); |
| if (rInfo.activityInfo != null && |
| rInfo.activityInfo.applicationInfo != null && |
| (rInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { |
| defaultRI = rInfo; |
| } |
| |
| if (mDisablePermissionDialogs) { |
| // bypass dialog and launch the only matching activity |
| rInfo = matches.get(0); |
| if (rInfo.activityInfo != null) { |
| defaultPackage = rInfo.activityInfo.packageName; |
| } |
| } |
| } |
| |
| if (defaultRI == null && defaultPackage != null) { |
| // look for default activity |
| for (int i = 0; i < count; i++) { |
| ResolveInfo rInfo = matches.get(i); |
| if (rInfo.activityInfo != null && |
| defaultPackage.equals(rInfo.activityInfo.packageName)) { |
| defaultRI = rInfo; |
| break; |
| } |
| } |
| } |
| |
| if (defaultRI != null) { |
| // grant permission for default activity |
| if (device != null) { |
| grantDevicePermission(device, defaultRI.activityInfo.applicationInfo.uid); |
| } else if (accessory != null) { |
| grantAccessoryPermission(accessory, defaultRI.activityInfo.applicationInfo.uid); |
| } |
| |
| // start default activity directly |
| try { |
| intent.setComponent( |
| new ComponentName(defaultRI.activityInfo.packageName, |
| defaultRI.activityInfo.name)); |
| mUserContext.startActivityAsUser(intent, mUser); |
| } catch (ActivityNotFoundException e) { |
| Slog.e(TAG, "startActivity failed", e); |
| } |
| } else { |
| Intent resolverIntent = new Intent(); |
| resolverIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| |
| if (count == 1) { |
| // start UsbConfirmActivity if there is only one choice |
| resolverIntent.setClassName("com.android.systemui", |
| "com.android.systemui.usb.UsbConfirmActivity"); |
| resolverIntent.putExtra("rinfo", matches.get(0)); |
| |
| if (device != null) { |
| resolverIntent.putExtra(UsbManager.EXTRA_DEVICE, device); |
| } else { |
| resolverIntent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); |
| } |
| } else { |
| // start UsbResolverActivity so user can choose an activity |
| resolverIntent.setClassName("com.android.systemui", |
| "com.android.systemui.usb.UsbResolverActivity"); |
| resolverIntent.putParcelableArrayListExtra("rlist", matches); |
| resolverIntent.putExtra(Intent.EXTRA_INTENT, intent); |
| } |
| try { |
| mUserContext.startActivityAsUser(resolverIntent, mUser); |
| } catch (ActivityNotFoundException e) { |
| Slog.e(TAG, "unable to start activity " + resolverIntent); |
| } |
| } |
| } |
| |
| private boolean clearCompatibleMatchesLocked(String packageName, DeviceFilter filter) { |
| boolean changed = false; |
| for (DeviceFilter test : mDevicePreferenceMap.keySet()) { |
| if (filter.matches(test)) { |
| mDevicePreferenceMap.remove(test); |
| changed = true; |
| } |
| } |
| return changed; |
| } |
| |
| private boolean clearCompatibleMatchesLocked(String packageName, AccessoryFilter filter) { |
| boolean changed = false; |
| for (AccessoryFilter test : mAccessoryPreferenceMap.keySet()) { |
| if (filter.matches(test)) { |
| mAccessoryPreferenceMap.remove(test); |
| changed = true; |
| } |
| } |
| return changed; |
| } |
| |
| private boolean handlePackageUpdateLocked(String packageName, ActivityInfo aInfo, |
| String metaDataName) { |
| XmlResourceParser parser = null; |
| boolean changed = false; |
| |
| try { |
| parser = aInfo.loadXmlMetaData(mPackageManager, metaDataName); |
| if (parser == null) return false; |
| |
| XmlUtils.nextElement(parser); |
| while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { |
| String tagName = parser.getName(); |
| if ("usb-device".equals(tagName)) { |
| DeviceFilter filter = DeviceFilter.read(parser); |
| if (clearCompatibleMatchesLocked(packageName, filter)) { |
| changed = true; |
| } |
| } |
| else if ("usb-accessory".equals(tagName)) { |
| AccessoryFilter filter = AccessoryFilter.read(parser); |
| if (clearCompatibleMatchesLocked(packageName, filter)) { |
| changed = true; |
| } |
| } |
| XmlUtils.nextElement(parser); |
| } |
| } catch (Exception e) { |
| Slog.w(TAG, "Unable to load component info " + aInfo.toString(), e); |
| } finally { |
| if (parser != null) parser.close(); |
| } |
| return changed; |
| } |
| |
| // Check to see if the package supports any USB devices or accessories. |
| // If so, clear any non-matching preferences for matching devices/accessories. |
| private void handlePackageUpdate(String packageName) { |
| synchronized (mLock) { |
| PackageInfo info; |
| boolean changed = false; |
| |
| try { |
| info = mPackageManager.getPackageInfo(packageName, |
| PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA); |
| } catch (NameNotFoundException e) { |
| Slog.e(TAG, "handlePackageUpdate could not find package " + packageName, e); |
| return; |
| } |
| |
| ActivityInfo[] activities = info.activities; |
| if (activities == null) return; |
| for (int i = 0; i < activities.length; i++) { |
| // check for meta-data, both for devices and accessories |
| if (handlePackageUpdateLocked(packageName, activities[i], |
| UsbManager.ACTION_USB_DEVICE_ATTACHED)) { |
| changed = true; |
| } |
| if (handlePackageUpdateLocked(packageName, activities[i], |
| UsbManager.ACTION_USB_ACCESSORY_ATTACHED)) { |
| changed = true; |
| } |
| } |
| |
| if (changed) { |
| writeSettingsLocked(); |
| } |
| } |
| } |
| |
| public boolean hasPermission(UsbDevice device) { |
| synchronized (mLock) { |
| int uid = Binder.getCallingUid(); |
| if (uid == Process.SYSTEM_UID || mDisablePermissionDialogs) { |
| return true; |
| } |
| SparseBooleanArray uidList = mDevicePermissionMap.get(device.getDeviceName()); |
| if (uidList == null) { |
| return false; |
| } |
| return uidList.get(uid); |
| } |
| } |
| |
| public boolean hasPermission(UsbAccessory accessory) { |
| synchronized (mLock) { |
| int uid = Binder.getCallingUid(); |
| if (uid == Process.SYSTEM_UID || mDisablePermissionDialogs) { |
| return true; |
| } |
| SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory); |
| if (uidList == null) { |
| return false; |
| } |
| return uidList.get(uid); |
| } |
| } |
| |
| public void checkPermission(UsbDevice device) { |
| if (!hasPermission(device)) { |
| throw new SecurityException("User has not given permission to device " + device); |
| } |
| } |
| |
| public void checkPermission(UsbAccessory accessory) { |
| if (!hasPermission(accessory)) { |
| throw new SecurityException("User has not given permission to accessory " + accessory); |
| } |
| } |
| |
| private void requestPermissionDialog(Intent intent, String packageName, PendingIntent pi) { |
| final int uid = Binder.getCallingUid(); |
| |
| // compare uid with packageName to foil apps pretending to be someone else |
| try { |
| ApplicationInfo aInfo = mPackageManager.getApplicationInfo(packageName, 0); |
| if (aInfo.uid != uid) { |
| throw new IllegalArgumentException("package " + packageName + |
| " does not match caller's uid " + uid); |
| } |
| } catch (PackageManager.NameNotFoundException e) { |
| throw new IllegalArgumentException("package " + packageName + " not found"); |
| } |
| |
| long identity = Binder.clearCallingIdentity(); |
| intent.setClassName("com.android.systemui", |
| "com.android.systemui.usb.UsbPermissionActivity"); |
| intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| intent.putExtra(Intent.EXTRA_INTENT, pi); |
| intent.putExtra("package", packageName); |
| intent.putExtra(Intent.EXTRA_UID, uid); |
| try { |
| mUserContext.startActivityAsUser(intent, mUser); |
| } catch (ActivityNotFoundException e) { |
| Slog.e(TAG, "unable to start UsbPermissionActivity"); |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| } |
| |
| public void requestPermission(UsbDevice device, String packageName, PendingIntent pi) { |
| Intent intent = new Intent(); |
| |
| // respond immediately if permission has already been granted |
| if (hasPermission(device)) { |
| intent.putExtra(UsbManager.EXTRA_DEVICE, device); |
| intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true); |
| try { |
| pi.send(mUserContext, 0, intent); |
| } catch (PendingIntent.CanceledException e) { |
| if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled"); |
| } |
| return; |
| } |
| |
| // start UsbPermissionActivity so user can choose an activity |
| intent.putExtra(UsbManager.EXTRA_DEVICE, device); |
| requestPermissionDialog(intent, packageName, pi); |
| } |
| |
| public void requestPermission(UsbAccessory accessory, String packageName, PendingIntent pi) { |
| Intent intent = new Intent(); |
| |
| // respond immediately if permission has already been granted |
| if (hasPermission(accessory)) { |
| intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); |
| intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true); |
| try { |
| pi.send(mUserContext, 0, intent); |
| } catch (PendingIntent.CanceledException e) { |
| if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled"); |
| } |
| return; |
| } |
| |
| intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); |
| requestPermissionDialog(intent, packageName, pi); |
| } |
| |
| public void setDevicePackage(UsbDevice device, String packageName) { |
| DeviceFilter filter = new DeviceFilter(device); |
| boolean changed = false; |
| synchronized (mLock) { |
| if (packageName == null) { |
| changed = (mDevicePreferenceMap.remove(filter) != null); |
| } else { |
| changed = !packageName.equals(mDevicePreferenceMap.get(filter)); |
| if (changed) { |
| mDevicePreferenceMap.put(filter, packageName); |
| } |
| } |
| if (changed) { |
| writeSettingsLocked(); |
| } |
| } |
| } |
| |
| public void setAccessoryPackage(UsbAccessory accessory, String packageName) { |
| AccessoryFilter filter = new AccessoryFilter(accessory); |
| boolean changed = false; |
| synchronized (mLock) { |
| if (packageName == null) { |
| changed = (mAccessoryPreferenceMap.remove(filter) != null); |
| } else { |
| changed = !packageName.equals(mAccessoryPreferenceMap.get(filter)); |
| if (changed) { |
| mAccessoryPreferenceMap.put(filter, packageName); |
| } |
| } |
| if (changed) { |
| writeSettingsLocked(); |
| } |
| } |
| } |
| |
| public void grantDevicePermission(UsbDevice device, int uid) { |
| synchronized (mLock) { |
| String deviceName = device.getDeviceName(); |
| SparseBooleanArray uidList = mDevicePermissionMap.get(deviceName); |
| if (uidList == null) { |
| uidList = new SparseBooleanArray(1); |
| mDevicePermissionMap.put(deviceName, uidList); |
| } |
| uidList.put(uid, true); |
| } |
| } |
| |
| public void grantAccessoryPermission(UsbAccessory accessory, int uid) { |
| synchronized (mLock) { |
| SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory); |
| if (uidList == null) { |
| uidList = new SparseBooleanArray(1); |
| mAccessoryPermissionMap.put(accessory, uidList); |
| } |
| uidList.put(uid, true); |
| } |
| } |
| |
| public boolean hasDefaults(String packageName) { |
| synchronized (mLock) { |
| if (mDevicePreferenceMap.values().contains(packageName)) return true; |
| if (mAccessoryPreferenceMap.values().contains(packageName)) return true; |
| return false; |
| } |
| } |
| |
| public void clearDefaults(String packageName) { |
| synchronized (mLock) { |
| if (clearPackageDefaultsLocked(packageName)) { |
| writeSettingsLocked(); |
| } |
| } |
| } |
| |
| private boolean clearPackageDefaultsLocked(String packageName) { |
| boolean cleared = false; |
| synchronized (mLock) { |
| if (mDevicePreferenceMap.containsValue(packageName)) { |
| // make a copy of the key set to avoid ConcurrentModificationException |
| Object[] keys = mDevicePreferenceMap.keySet().toArray(); |
| for (int i = 0; i < keys.length; i++) { |
| Object key = keys[i]; |
| if (packageName.equals(mDevicePreferenceMap.get(key))) { |
| mDevicePreferenceMap.remove(key); |
| cleared = true; |
| } |
| } |
| } |
| if (mAccessoryPreferenceMap.containsValue(packageName)) { |
| // make a copy of the key set to avoid ConcurrentModificationException |
| Object[] keys = mAccessoryPreferenceMap.keySet().toArray(); |
| for (int i = 0; i < keys.length; i++) { |
| Object key = keys[i]; |
| if (packageName.equals(mAccessoryPreferenceMap.get(key))) { |
| mAccessoryPreferenceMap.remove(key); |
| cleared = true; |
| } |
| } |
| } |
| return cleared; |
| } |
| } |
| |
| public void dump(IndentingPrintWriter pw) { |
| synchronized (mLock) { |
| pw.println("Device permissions:"); |
| for (String deviceName : mDevicePermissionMap.keySet()) { |
| pw.print(" " + deviceName + ": "); |
| SparseBooleanArray uidList = mDevicePermissionMap.get(deviceName); |
| int count = uidList.size(); |
| for (int i = 0; i < count; i++) { |
| pw.print(Integer.toString(uidList.keyAt(i)) + " "); |
| } |
| pw.println(); |
| } |
| pw.println("Accessory permissions:"); |
| for (UsbAccessory accessory : mAccessoryPermissionMap.keySet()) { |
| pw.print(" " + accessory + ": "); |
| SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory); |
| int count = uidList.size(); |
| for (int i = 0; i < count; i++) { |
| pw.print(Integer.toString(uidList.keyAt(i)) + " "); |
| } |
| pw.println(); |
| } |
| pw.println("Device preferences:"); |
| for (DeviceFilter filter : mDevicePreferenceMap.keySet()) { |
| pw.println(" " + filter + ": " + mDevicePreferenceMap.get(filter)); |
| } |
| pw.println("Accessory preferences:"); |
| for (AccessoryFilter filter : mAccessoryPreferenceMap.keySet()) { |
| pw.println(" " + filter + ": " + mAccessoryPreferenceMap.get(filter)); |
| } |
| } |
| } |
| |
| private static Intent createDeviceAttachedIntent(UsbDevice device) { |
| Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED); |
| intent.putExtra(UsbManager.EXTRA_DEVICE, device); |
| intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| return intent; |
| } |
| } |