1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.usb;
18
19import android.app.PendingIntent;
20import android.content.ActivityNotFoundException;
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.Intent;
24import android.content.pm.ActivityInfo;
25import android.content.pm.ApplicationInfo;
26import android.content.pm.PackageInfo;
27import android.content.pm.PackageManager;
28import android.content.pm.PackageManager.NameNotFoundException;
29import android.content.pm.ResolveInfo;
30import android.content.res.XmlResourceParser;
31import android.hardware.usb.UsbAccessory;
32import android.hardware.usb.UsbDevice;
33import android.hardware.usb.UsbInterface;
34import android.hardware.usb.UsbManager;
35import android.os.Binder;
36import android.os.Environment;
37import android.os.Process;
38import android.os.UserHandle;
39import android.util.AtomicFile;
40import android.util.Log;
41import android.util.Slog;
42import android.util.SparseBooleanArray;
43import android.util.Xml;
44
45import com.android.internal.content.PackageMonitor;
46import com.android.internal.util.FastXmlSerializer;
47import com.android.internal.util.IndentingPrintWriter;
48import com.android.internal.util.XmlUtils;
49
50import org.xmlpull.v1.XmlPullParser;
51import org.xmlpull.v1.XmlPullParserException;
52import org.xmlpull.v1.XmlSerializer;
53
54import java.io.File;
55import java.io.FileInputStream;
56import java.io.FileNotFoundException;
57import java.io.FileOutputStream;
58import java.io.IOException;
59import java.nio.charset.StandardCharsets;
60import java.util.ArrayList;
61import java.util.HashMap;
62import java.util.List;
63
64import libcore.io.IoUtils;
65
66class UsbSettingsManager {
67    private static final String TAG = "UsbSettingsManager";
68    private static final boolean DEBUG = false;
69
70    /** Legacy settings file, before multi-user */
71    private static final File sSingleUserSettingsFile = new File(
72            "/data/system/usb_device_manager.xml");
73
74    private final UserHandle mUser;
75    private final AtomicFile mSettingsFile;
76    private final boolean mDisablePermissionDialogs;
77
78    private final Context mContext;
79    private final Context mUserContext;
80    private final PackageManager mPackageManager;
81
82    // Temporary mapping USB device name to list of UIDs with permissions for the device
83    private final HashMap<String, SparseBooleanArray> mDevicePermissionMap =
84            new HashMap<String, SparseBooleanArray>();
85    // Temporary mapping UsbAccessory to list of UIDs with permissions for the accessory
86    private final HashMap<UsbAccessory, SparseBooleanArray> mAccessoryPermissionMap =
87            new HashMap<UsbAccessory, SparseBooleanArray>();
88    // Maps DeviceFilter to user preferred application package
89    private final HashMap<DeviceFilter, String> mDevicePreferenceMap =
90            new HashMap<DeviceFilter, String>();
91    // Maps AccessoryFilter to user preferred application package
92    private final HashMap<AccessoryFilter, String> mAccessoryPreferenceMap =
93            new HashMap<AccessoryFilter, String>();
94
95    private final Object mLock = new Object();
96
97    // This class is used to describe a USB device.
98    // When used in HashMaps all values must be specified,
99    // but wildcards can be used for any of the fields in
100    // the package meta-data.
101    private static class DeviceFilter {
102        // USB Vendor ID (or -1 for unspecified)
103        public final int mVendorId;
104        // USB Product ID (or -1 for unspecified)
105        public final int mProductId;
106        // USB device or interface class (or -1 for unspecified)
107        public final int mClass;
108        // USB device subclass (or -1 for unspecified)
109        public final int mSubclass;
110        // USB device protocol (or -1 for unspecified)
111        public final int mProtocol;
112        // USB device manufacturer name string (or null for unspecified)
113        public final String mManufacturerName;
114        // USB device product name string (or null for unspecified)
115        public final String mProductName;
116        // USB device serial number string (or null for unspecified)
117        public final String mSerialNumber;
118
119        public DeviceFilter(int vid, int pid, int clasz, int subclass, int protocol,
120                            String manufacturer, String product, String serialnum) {
121            mVendorId = vid;
122            mProductId = pid;
123            mClass = clasz;
124            mSubclass = subclass;
125            mProtocol = protocol;
126            mManufacturerName = manufacturer;
127            mProductName = product;
128            mSerialNumber = serialnum;
129        }
130
131        public DeviceFilter(UsbDevice device) {
132            mVendorId = device.getVendorId();
133            mProductId = device.getProductId();
134            mClass = device.getDeviceClass();
135            mSubclass = device.getDeviceSubclass();
136            mProtocol = device.getDeviceProtocol();
137            mManufacturerName = device.getManufacturerName();
138            mProductName = device.getProductName();
139            mSerialNumber = device.getSerialNumber();
140        }
141
142        public static DeviceFilter read(XmlPullParser parser)
143                throws XmlPullParserException, IOException {
144            int vendorId = -1;
145            int productId = -1;
146            int deviceClass = -1;
147            int deviceSubclass = -1;
148            int deviceProtocol = -1;
149            String manufacturerName = null;
150            String productName = null;
151            String serialNumber = null;
152
153            int count = parser.getAttributeCount();
154            for (int i = 0; i < count; i++) {
155                String name = parser.getAttributeName(i);
156                String value = parser.getAttributeValue(i);
157                // Attribute values are ints or strings
158                if ("manufacturer-name".equals(name)) {
159                    manufacturerName = value;
160                } else if ("product-name".equals(name)) {
161                    productName = value;
162                } else if ("serial-number".equals(name)) {
163                    serialNumber = value;
164                } else {
165                    int intValue = -1;
166                    int radix = 10;
167                    if (value != null && value.length() > 2 && value.charAt(0) == '0' &&
168                        (value.charAt(1) == 'x' || value.charAt(1) == 'X')) {
169                        // allow hex values starting with 0x or 0X
170                        radix = 16;
171                        value = value.substring(2);
172                    }
173                    try {
174                        intValue = Integer.parseInt(value, radix);
175                    } catch (NumberFormatException e) {
176                        Slog.e(TAG, "invalid number for field " + name, e);
177                        continue;
178                    }
179                    if ("vendor-id".equals(name)) {
180                        vendorId = intValue;
181                    } else if ("product-id".equals(name)) {
182                        productId = intValue;
183                    } else if ("class".equals(name)) {
184                        deviceClass = intValue;
185                    } else if ("subclass".equals(name)) {
186                        deviceSubclass = intValue;
187                    } else if ("protocol".equals(name)) {
188                        deviceProtocol = intValue;
189                    }
190                }
191            }
192            return new DeviceFilter(vendorId, productId,
193                    deviceClass, deviceSubclass, deviceProtocol,
194                    manufacturerName, productName, serialNumber);
195        }
196
197        public void write(XmlSerializer serializer) throws IOException {
198            serializer.startTag(null, "usb-device");
199            if (mVendorId != -1) {
200                serializer.attribute(null, "vendor-id", Integer.toString(mVendorId));
201            }
202            if (mProductId != -1) {
203                serializer.attribute(null, "product-id", Integer.toString(mProductId));
204            }
205            if (mClass != -1) {
206                serializer.attribute(null, "class", Integer.toString(mClass));
207            }
208            if (mSubclass != -1) {
209                serializer.attribute(null, "subclass", Integer.toString(mSubclass));
210            }
211            if (mProtocol != -1) {
212                serializer.attribute(null, "protocol", Integer.toString(mProtocol));
213            }
214            if (mManufacturerName != null) {
215                serializer.attribute(null, "manufacturer-name", mManufacturerName);
216            }
217            if (mProductName != null) {
218                serializer.attribute(null, "product-name", mProductName);
219            }
220            if (mSerialNumber != null) {
221                serializer.attribute(null, "serial-number", mSerialNumber);
222            }
223            serializer.endTag(null, "usb-device");
224        }
225
226        private boolean matches(int clasz, int subclass, int protocol) {
227            return ((mClass == -1 || clasz == mClass) &&
228                    (mSubclass == -1 || subclass == mSubclass) &&
229                    (mProtocol == -1 || protocol == mProtocol));
230        }
231
232        public boolean matches(UsbDevice device) {
233            if (mVendorId != -1 && device.getVendorId() != mVendorId) return false;
234            if (mProductId != -1 && device.getProductId() != mProductId) return false;
235            if (mManufacturerName != null && device.getManufacturerName() == null) return false;
236            if (mProductName != null && device.getProductName() == null) return false;
237            if (mSerialNumber != null && device.getSerialNumber() == null) return false;
238            if (mManufacturerName != null && device.getManufacturerName() != null &&
239                !mManufacturerName.equals(device.getManufacturerName())) return false;
240            if (mProductName != null && device.getProductName() != null &&
241                !mProductName.equals(device.getProductName())) return false;
242            if (mSerialNumber != null && device.getSerialNumber() != null &&
243                !mSerialNumber.equals(device.getSerialNumber())) return false;
244
245            // check device class/subclass/protocol
246            if (matches(device.getDeviceClass(), device.getDeviceSubclass(),
247                    device.getDeviceProtocol())) return true;
248
249            // if device doesn't match, check the interfaces
250            int count = device.getInterfaceCount();
251            for (int i = 0; i < count; i++) {
252                UsbInterface intf = device.getInterface(i);
253                 if (matches(intf.getInterfaceClass(), intf.getInterfaceSubclass(),
254                        intf.getInterfaceProtocol())) return true;
255            }
256
257            return false;
258        }
259
260        public boolean matches(DeviceFilter f) {
261            if (mVendorId != -1 && f.mVendorId != mVendorId) return false;
262            if (mProductId != -1 && f.mProductId != mProductId) return false;
263            if (f.mManufacturerName != null && mManufacturerName == null) return false;
264            if (f.mProductName != null && mProductName == null) return false;
265            if (f.mSerialNumber != null && mSerialNumber == null) return false;
266            if (mManufacturerName != null && f.mManufacturerName != null &&
267                !mManufacturerName.equals(f.mManufacturerName)) return false;
268            if (mProductName != null && f.mProductName != null &&
269                !mProductName.equals(f.mProductName)) return false;
270            if (mSerialNumber != null && f.mSerialNumber != null &&
271                !mSerialNumber.equals(f.mSerialNumber)) return false;
272
273            // check device class/subclass/protocol
274            return matches(f.mClass, f.mSubclass, f.mProtocol);
275        }
276
277        @Override
278        public boolean equals(Object obj) {
279            // can't compare if we have wildcard strings
280            if (mVendorId == -1 || mProductId == -1 ||
281                    mClass == -1 || mSubclass == -1 || mProtocol == -1) {
282                return false;
283            }
284            if (obj instanceof DeviceFilter) {
285                DeviceFilter filter = (DeviceFilter)obj;
286
287                if (filter.mVendorId != mVendorId ||
288                        filter.mProductId != mProductId ||
289                        filter.mClass != mClass ||
290                        filter.mSubclass != mSubclass ||
291                        filter.mProtocol != mProtocol) {
292                    return(false);
293                }
294                if ((filter.mManufacturerName != null &&
295                        mManufacturerName == null) ||
296                    (filter.mManufacturerName == null &&
297                        mManufacturerName != null) ||
298                    (filter.mProductName != null &&
299                        mProductName == null)  ||
300                    (filter.mProductName == null &&
301                        mProductName != null) ||
302                    (filter.mSerialNumber != null &&
303                        mSerialNumber == null)  ||
304                    (filter.mSerialNumber == null &&
305                        mSerialNumber != null)) {
306                    return(false);
307                }
308                if  ((filter.mManufacturerName != null &&
309                        mManufacturerName != null &&
310                        !mManufacturerName.equals(filter.mManufacturerName)) ||
311                     (filter.mProductName != null &&
312                        mProductName != null &&
313                        !mProductName.equals(filter.mProductName)) ||
314                     (filter.mSerialNumber != null &&
315                        mSerialNumber != null &&
316                        !mSerialNumber.equals(filter.mSerialNumber))) {
317                    return(false);
318                }
319                return(true);
320            }
321            if (obj instanceof UsbDevice) {
322                UsbDevice device = (UsbDevice)obj;
323                if (device.getVendorId() != mVendorId ||
324                        device.getProductId() != mProductId ||
325                        device.getDeviceClass() != mClass ||
326                        device.getDeviceSubclass() != mSubclass ||
327                        device.getDeviceProtocol() != mProtocol) {
328                    return(false);
329                }
330                if ((mManufacturerName != null && device.getManufacturerName() == null) ||
331                        (mManufacturerName == null && device.getManufacturerName() != null) ||
332                        (mProductName != null && device.getProductName() == null) ||
333                        (mProductName == null && device.getProductName() != null) ||
334                        (mSerialNumber != null && device.getSerialNumber() == null) ||
335                        (mSerialNumber == null && device.getSerialNumber() != null)) {
336                    return(false);
337                }
338                if ((device.getManufacturerName() != null &&
339                        !mManufacturerName.equals(device.getManufacturerName())) ||
340                        (device.getProductName() != null &&
341                            !mProductName.equals(device.getProductName())) ||
342                        (device.getSerialNumber() != null &&
343                            !mSerialNumber.equals(device.getSerialNumber()))) {
344                    return(false);
345                }
346                return true;
347            }
348            return false;
349        }
350
351        @Override
352        public int hashCode() {
353            return (((mVendorId << 16) | mProductId) ^
354                    ((mClass << 16) | (mSubclass << 8) | mProtocol));
355        }
356
357        @Override
358        public String toString() {
359            return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + mProductId +
360                    ",mClass=" + mClass + ",mSubclass=" + mSubclass +
361                    ",mProtocol=" + mProtocol + ",mManufacturerName=" + mManufacturerName +
362                    ",mProductName=" + mProductName + ",mSerialNumber=" + mSerialNumber +
363                    "]";
364        }
365    }
366
367    // This class is used to describe a USB accessory.
368    // When used in HashMaps all values must be specified,
369    // but wildcards can be used for any of the fields in
370    // the package meta-data.
371    private static class AccessoryFilter {
372        // USB accessory manufacturer (or null for unspecified)
373        public final String mManufacturer;
374        // USB accessory model (or null for unspecified)
375        public final String mModel;
376        // USB accessory version (or null for unspecified)
377        public final String mVersion;
378
379        public AccessoryFilter(String manufacturer, String model, String version) {
380            mManufacturer = manufacturer;
381            mModel = model;
382            mVersion = version;
383        }
384
385        public AccessoryFilter(UsbAccessory accessory) {
386            mManufacturer = accessory.getManufacturer();
387            mModel = accessory.getModel();
388            mVersion = accessory.getVersion();
389        }
390
391        public static AccessoryFilter read(XmlPullParser parser)
392                throws XmlPullParserException, IOException {
393            String manufacturer = null;
394            String model = null;
395            String version = null;
396
397            int count = parser.getAttributeCount();
398            for (int i = 0; i < count; i++) {
399                String name = parser.getAttributeName(i);
400                String value = parser.getAttributeValue(i);
401
402                if ("manufacturer".equals(name)) {
403                    manufacturer = value;
404                } else if ("model".equals(name)) {
405                    model = value;
406                } else if ("version".equals(name)) {
407                    version = value;
408                }
409             }
410             return new AccessoryFilter(manufacturer, model, version);
411        }
412
413        public void write(XmlSerializer serializer)throws IOException {
414            serializer.startTag(null, "usb-accessory");
415            if (mManufacturer != null) {
416                serializer.attribute(null, "manufacturer", mManufacturer);
417            }
418            if (mModel != null) {
419                serializer.attribute(null, "model", mModel);
420            }
421            if (mVersion != null) {
422                serializer.attribute(null, "version", mVersion);
423            }
424            serializer.endTag(null, "usb-accessory");
425        }
426
427        public boolean matches(UsbAccessory acc) {
428            if (mManufacturer != null && !acc.getManufacturer().equals(mManufacturer)) return false;
429            if (mModel != null && !acc.getModel().equals(mModel)) return false;
430            if (mVersion != null && !acc.getVersion().equals(mVersion)) return false;
431            return true;
432        }
433
434        public boolean matches(AccessoryFilter f) {
435            if (mManufacturer != null && !f.mManufacturer.equals(mManufacturer)) return false;
436            if (mModel != null && !f.mModel.equals(mModel)) return false;
437            if (mVersion != null && !f.mVersion.equals(mVersion)) return false;
438            return true;
439        }
440
441        @Override
442        public boolean equals(Object obj) {
443            // can't compare if we have wildcard strings
444            if (mManufacturer == null || mModel == null || mVersion == null) {
445                return false;
446            }
447            if (obj instanceof AccessoryFilter) {
448                AccessoryFilter filter = (AccessoryFilter)obj;
449                return (mManufacturer.equals(filter.mManufacturer) &&
450                        mModel.equals(filter.mModel) &&
451                        mVersion.equals(filter.mVersion));
452            }
453            if (obj instanceof UsbAccessory) {
454                UsbAccessory accessory = (UsbAccessory)obj;
455                return (mManufacturer.equals(accessory.getManufacturer()) &&
456                        mModel.equals(accessory.getModel()) &&
457                        mVersion.equals(accessory.getVersion()));
458            }
459            return false;
460        }
461
462        @Override
463        public int hashCode() {
464            return ((mManufacturer == null ? 0 : mManufacturer.hashCode()) ^
465                    (mModel == null ? 0 : mModel.hashCode()) ^
466                    (mVersion == null ? 0 : mVersion.hashCode()));
467        }
468
469        @Override
470        public String toString() {
471            return "AccessoryFilter[mManufacturer=\"" + mManufacturer +
472                                "\", mModel=\"" + mModel +
473                                "\", mVersion=\"" + mVersion + "\"]";
474        }
475    }
476
477    private class MyPackageMonitor extends PackageMonitor {
478        @Override
479        public void onPackageAdded(String packageName, int uid) {
480            handlePackageUpdate(packageName);
481        }
482
483        @Override
484        public boolean onPackageChanged(String packageName, int uid, String[] components) {
485            handlePackageUpdate(packageName);
486            return false;
487        }
488
489        @Override
490        public void onPackageRemoved(String packageName, int uid) {
491            clearDefaults(packageName);
492        }
493    }
494
495    MyPackageMonitor mPackageMonitor = new MyPackageMonitor();
496
497    private final MtpNotificationManager mMtpNotificationManager;
498
499    public UsbSettingsManager(Context context, UserHandle user) {
500        if (DEBUG) Slog.v(TAG, "Creating settings for " + user);
501
502        try {
503            mUserContext = context.createPackageContextAsUser("android", 0, user);
504        } catch (NameNotFoundException e) {
505            throw new RuntimeException("Missing android package");
506        }
507
508        mContext = context;
509        mPackageManager = mUserContext.getPackageManager();
510
511        mUser = user;
512        mSettingsFile = new AtomicFile(new File(
513                Environment.getUserSystemDirectory(user.getIdentifier()),
514                "usb_device_manager.xml"));
515
516        mDisablePermissionDialogs = context.getResources().getBoolean(
517                com.android.internal.R.bool.config_disableUsbPermissionDialogs);
518
519        synchronized (mLock) {
520            if (UserHandle.SYSTEM.equals(user)) {
521                upgradeSingleUserLocked();
522            }
523            readSettingsLocked();
524        }
525
526        mPackageMonitor.register(mUserContext, null, true);
527        mMtpNotificationManager = new MtpNotificationManager(
528                context,
529                new MtpNotificationManager.OnOpenInAppListener() {
530                    @Override
531                    public void onOpenInApp(UsbDevice device) {
532                        resolveActivity(createDeviceAttachedIntent(device), device);
533                    }
534                });
535    }
536
537    private void readPreference(XmlPullParser parser)
538            throws XmlPullParserException, IOException {
539        String packageName = null;
540        int count = parser.getAttributeCount();
541        for (int i = 0; i < count; i++) {
542            if ("package".equals(parser.getAttributeName(i))) {
543                packageName = parser.getAttributeValue(i);
544                break;
545            }
546        }
547        XmlUtils.nextElement(parser);
548        if ("usb-device".equals(parser.getName())) {
549            DeviceFilter filter = DeviceFilter.read(parser);
550            mDevicePreferenceMap.put(filter, packageName);
551        } else if ("usb-accessory".equals(parser.getName())) {
552            AccessoryFilter filter = AccessoryFilter.read(parser);
553            mAccessoryPreferenceMap.put(filter, packageName);
554        }
555        XmlUtils.nextElement(parser);
556    }
557
558    /**
559     * Upgrade any single-user settings from {@link #sSingleUserSettingsFile}.
560     * Should only by called by owner.
561     */
562    private void upgradeSingleUserLocked() {
563        if (sSingleUserSettingsFile.exists()) {
564            mDevicePreferenceMap.clear();
565            mAccessoryPreferenceMap.clear();
566
567            FileInputStream fis = null;
568            try {
569                fis = new FileInputStream(sSingleUserSettingsFile);
570                XmlPullParser parser = Xml.newPullParser();
571                parser.setInput(fis, StandardCharsets.UTF_8.name());
572
573                XmlUtils.nextElement(parser);
574                while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
575                    final String tagName = parser.getName();
576                    if ("preference".equals(tagName)) {
577                        readPreference(parser);
578                    } else {
579                        XmlUtils.nextElement(parser);
580                    }
581                }
582            } catch (IOException e) {
583                Log.wtf(TAG, "Failed to read single-user settings", e);
584            } catch (XmlPullParserException e) {
585                Log.wtf(TAG, "Failed to read single-user settings", e);
586            } finally {
587                IoUtils.closeQuietly(fis);
588            }
589
590            writeSettingsLocked();
591
592            // Success or failure, we delete single-user file
593            sSingleUserSettingsFile.delete();
594        }
595    }
596
597    private void readSettingsLocked() {
598        if (DEBUG) Slog.v(TAG, "readSettingsLocked()");
599
600        mDevicePreferenceMap.clear();
601        mAccessoryPreferenceMap.clear();
602
603        FileInputStream stream = null;
604        try {
605            stream = mSettingsFile.openRead();
606            XmlPullParser parser = Xml.newPullParser();
607            parser.setInput(stream, StandardCharsets.UTF_8.name());
608
609            XmlUtils.nextElement(parser);
610            while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
611                String tagName = parser.getName();
612                if ("preference".equals(tagName)) {
613                    readPreference(parser);
614                } else {
615                    XmlUtils.nextElement(parser);
616                }
617            }
618        } catch (FileNotFoundException e) {
619            if (DEBUG) Slog.d(TAG, "settings file not found");
620        } catch (Exception e) {
621            Slog.e(TAG, "error reading settings file, deleting to start fresh", e);
622            mSettingsFile.delete();
623        } finally {
624            IoUtils.closeQuietly(stream);
625        }
626    }
627
628    private void writeSettingsLocked() {
629        if (DEBUG) Slog.v(TAG, "writeSettingsLocked()");
630
631        FileOutputStream fos = null;
632        try {
633            fos = mSettingsFile.startWrite();
634
635            FastXmlSerializer serializer = new FastXmlSerializer();
636            serializer.setOutput(fos, StandardCharsets.UTF_8.name());
637            serializer.startDocument(null, true);
638            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
639            serializer.startTag(null, "settings");
640
641            for (DeviceFilter filter : mDevicePreferenceMap.keySet()) {
642                serializer.startTag(null, "preference");
643                serializer.attribute(null, "package", mDevicePreferenceMap.get(filter));
644                filter.write(serializer);
645                serializer.endTag(null, "preference");
646            }
647
648            for (AccessoryFilter filter : mAccessoryPreferenceMap.keySet()) {
649                serializer.startTag(null, "preference");
650                serializer.attribute(null, "package", mAccessoryPreferenceMap.get(filter));
651                filter.write(serializer);
652                serializer.endTag(null, "preference");
653            }
654
655            serializer.endTag(null, "settings");
656            serializer.endDocument();
657
658            mSettingsFile.finishWrite(fos);
659        } catch (IOException e) {
660            Slog.e(TAG, "Failed to write settings", e);
661            if (fos != null) {
662                mSettingsFile.failWrite(fos);
663            }
664        }
665    }
666
667    // Checks to see if a package matches a device or accessory.
668    // Only one of device and accessory should be non-null.
669    private boolean packageMatchesLocked(ResolveInfo info, String metaDataName,
670            UsbDevice device, UsbAccessory accessory) {
671        ActivityInfo ai = info.activityInfo;
672
673        XmlResourceParser parser = null;
674        try {
675            parser = ai.loadXmlMetaData(mPackageManager, metaDataName);
676            if (parser == null) {
677                Slog.w(TAG, "no meta-data for " + info);
678                return false;
679            }
680
681            XmlUtils.nextElement(parser);
682            while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
683                String tagName = parser.getName();
684                if (device != null && "usb-device".equals(tagName)) {
685                    DeviceFilter filter = DeviceFilter.read(parser);
686                    if (filter.matches(device)) {
687                        return true;
688                    }
689                }
690                else if (accessory != null && "usb-accessory".equals(tagName)) {
691                    AccessoryFilter filter = AccessoryFilter.read(parser);
692                    if (filter.matches(accessory)) {
693                        return true;
694                    }
695                }
696                XmlUtils.nextElement(parser);
697            }
698        } catch (Exception e) {
699            Slog.w(TAG, "Unable to load component info " + info.toString(), e);
700        } finally {
701            if (parser != null) parser.close();
702        }
703        return false;
704    }
705
706    private final ArrayList<ResolveInfo> getDeviceMatchesLocked(UsbDevice device, Intent intent) {
707        ArrayList<ResolveInfo> matches = new ArrayList<ResolveInfo>();
708        List<ResolveInfo> resolveInfos = mPackageManager.queryIntentActivities(intent,
709                PackageManager.GET_META_DATA);
710        int count = resolveInfos.size();
711        for (int i = 0; i < count; i++) {
712            ResolveInfo resolveInfo = resolveInfos.get(i);
713            if (packageMatchesLocked(resolveInfo, intent.getAction(), device, null)) {
714                matches.add(resolveInfo);
715            }
716        }
717        return matches;
718    }
719
720    private final ArrayList<ResolveInfo> getAccessoryMatchesLocked(
721            UsbAccessory accessory, Intent intent) {
722        ArrayList<ResolveInfo> matches = new ArrayList<ResolveInfo>();
723        List<ResolveInfo> resolveInfos = mPackageManager.queryIntentActivities(intent,
724                PackageManager.GET_META_DATA);
725        int count = resolveInfos.size();
726        for (int i = 0; i < count; i++) {
727            ResolveInfo resolveInfo = resolveInfos.get(i);
728            if (packageMatchesLocked(resolveInfo, intent.getAction(), null, accessory)) {
729                matches.add(resolveInfo);
730            }
731        }
732        return matches;
733    }
734
735    public void deviceAttached(UsbDevice device) {
736        final Intent intent = createDeviceAttachedIntent(device);
737
738        // Send broadcast to running activity with registered intent
739        mUserContext.sendBroadcast(intent);
740
741        if (MtpNotificationManager.shouldShowNotification(mPackageManager, device)) {
742            // Show notification if the device is MTP storage.
743            mMtpNotificationManager.showNotification(device);
744        } else {
745            resolveActivity(intent, device);
746        }
747    }
748
749    private void resolveActivity(Intent intent, UsbDevice device) {
750        ArrayList<ResolveInfo> matches;
751        String defaultPackage;
752        synchronized (mLock) {
753            matches = getDeviceMatchesLocked(device, intent);
754            // Launch our default activity directly, if we have one.
755            // Otherwise we will start the UsbResolverActivity to allow the user to choose.
756            defaultPackage = mDevicePreferenceMap.get(new DeviceFilter(device));
757        }
758
759        // Start activity with registered intent
760        resolveActivity(intent, matches, defaultPackage, device, null);
761    }
762
763    public void deviceDetached(UsbDevice device) {
764        // clear temporary permissions for the device
765        mDevicePermissionMap.remove(device.getDeviceName());
766
767        Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_DETACHED);
768        intent.putExtra(UsbManager.EXTRA_DEVICE, device);
769        if (DEBUG) Slog.d(TAG, "usbDeviceRemoved, sending " + intent);
770        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
771
772        mMtpNotificationManager.hideNotification(device.getDeviceId());
773    }
774
775    public void accessoryAttached(UsbAccessory accessory) {
776        Intent intent = new Intent(UsbManager.ACTION_USB_ACCESSORY_ATTACHED);
777        intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
778        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
779
780        ArrayList<ResolveInfo> matches;
781        String defaultPackage;
782        synchronized (mLock) {
783            matches = getAccessoryMatchesLocked(accessory, intent);
784            // Launch our default activity directly, if we have one.
785            // Otherwise we will start the UsbResolverActivity to allow the user to choose.
786            defaultPackage = mAccessoryPreferenceMap.get(new AccessoryFilter(accessory));
787        }
788
789        resolveActivity(intent, matches, defaultPackage, null, accessory);
790    }
791
792    public void accessoryDetached(UsbAccessory accessory) {
793        // clear temporary permissions for the accessory
794        mAccessoryPermissionMap.remove(accessory);
795
796        Intent intent = new Intent(
797                UsbManager.ACTION_USB_ACCESSORY_DETACHED);
798        intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
799        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
800    }
801
802    private void resolveActivity(Intent intent, ArrayList<ResolveInfo> matches,
803            String defaultPackage, UsbDevice device, UsbAccessory accessory) {
804        int count = matches.size();
805
806        // don't show the resolver activity if there are no choices available
807        if (count == 0) {
808            if (accessory != null) {
809                String uri = accessory.getUri();
810                if (uri != null && uri.length() > 0) {
811                    // display URI to user
812                    // start UsbResolverActivity so user can choose an activity
813                    Intent dialogIntent = new Intent();
814                    dialogIntent.setClassName("com.android.systemui",
815                            "com.android.systemui.usb.UsbAccessoryUriActivity");
816                    dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
817                    dialogIntent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
818                    dialogIntent.putExtra("uri", uri);
819                    try {
820                        mUserContext.startActivityAsUser(dialogIntent, mUser);
821                    } catch (ActivityNotFoundException e) {
822                        Slog.e(TAG, "unable to start UsbAccessoryUriActivity");
823                    }
824                }
825            }
826
827            // do nothing
828            return;
829        }
830
831        ResolveInfo defaultRI = null;
832        if (count == 1 && defaultPackage == null) {
833            // Check to see if our single choice is on the system partition.
834            // If so, treat it as our default without calling UsbResolverActivity
835            ResolveInfo rInfo = matches.get(0);
836            if (rInfo.activityInfo != null &&
837                    rInfo.activityInfo.applicationInfo != null &&
838                    (rInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
839                defaultRI = rInfo;
840            }
841
842            if (mDisablePermissionDialogs) {
843                // bypass dialog and launch the only matching activity
844                rInfo = matches.get(0);
845                if (rInfo.activityInfo != null) {
846                    defaultPackage = rInfo.activityInfo.packageName;
847                }
848            }
849        }
850
851        if (defaultRI == null && defaultPackage != null) {
852            // look for default activity
853            for (int i = 0; i < count; i++) {
854                ResolveInfo rInfo = matches.get(i);
855                if (rInfo.activityInfo != null &&
856                        defaultPackage.equals(rInfo.activityInfo.packageName)) {
857                    defaultRI = rInfo;
858                    break;
859                }
860            }
861        }
862
863        if (defaultRI != null) {
864            // grant permission for default activity
865            if (device != null) {
866                grantDevicePermission(device, defaultRI.activityInfo.applicationInfo.uid);
867            } else if (accessory != null) {
868                grantAccessoryPermission(accessory, defaultRI.activityInfo.applicationInfo.uid);
869            }
870
871            // start default activity directly
872            try {
873                intent.setComponent(
874                        new ComponentName(defaultRI.activityInfo.packageName,
875                                defaultRI.activityInfo.name));
876                mUserContext.startActivityAsUser(intent, mUser);
877            } catch (ActivityNotFoundException e) {
878                Slog.e(TAG, "startActivity failed", e);
879            }
880        } else {
881            Intent resolverIntent = new Intent();
882            resolverIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
883
884            if (count == 1) {
885                // start UsbConfirmActivity if there is only one choice
886                resolverIntent.setClassName("com.android.systemui",
887                        "com.android.systemui.usb.UsbConfirmActivity");
888                resolverIntent.putExtra("rinfo", matches.get(0));
889
890                if (device != null) {
891                    resolverIntent.putExtra(UsbManager.EXTRA_DEVICE, device);
892                } else {
893                    resolverIntent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
894                }
895            } else {
896                // start UsbResolverActivity so user can choose an activity
897                resolverIntent.setClassName("com.android.systemui",
898                        "com.android.systemui.usb.UsbResolverActivity");
899                resolverIntent.putParcelableArrayListExtra("rlist", matches);
900                resolverIntent.putExtra(Intent.EXTRA_INTENT, intent);
901            }
902            try {
903                mUserContext.startActivityAsUser(resolverIntent, mUser);
904            } catch (ActivityNotFoundException e) {
905                Slog.e(TAG, "unable to start activity " + resolverIntent);
906            }
907        }
908    }
909
910    private boolean clearCompatibleMatchesLocked(String packageName, DeviceFilter filter) {
911        boolean changed = false;
912        for (DeviceFilter test : mDevicePreferenceMap.keySet()) {
913            if (filter.matches(test)) {
914                mDevicePreferenceMap.remove(test);
915                changed = true;
916            }
917        }
918        return changed;
919    }
920
921    private boolean clearCompatibleMatchesLocked(String packageName, AccessoryFilter filter) {
922        boolean changed = false;
923        for (AccessoryFilter test : mAccessoryPreferenceMap.keySet()) {
924            if (filter.matches(test)) {
925                mAccessoryPreferenceMap.remove(test);
926                changed = true;
927            }
928        }
929        return changed;
930    }
931
932    private boolean handlePackageUpdateLocked(String packageName, ActivityInfo aInfo,
933            String metaDataName) {
934        XmlResourceParser parser = null;
935        boolean changed = false;
936
937        try {
938            parser = aInfo.loadXmlMetaData(mPackageManager, metaDataName);
939            if (parser == null) return false;
940
941            XmlUtils.nextElement(parser);
942            while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
943                String tagName = parser.getName();
944                if ("usb-device".equals(tagName)) {
945                    DeviceFilter filter = DeviceFilter.read(parser);
946                    if (clearCompatibleMatchesLocked(packageName, filter)) {
947                        changed = true;
948                    }
949                }
950                else if ("usb-accessory".equals(tagName)) {
951                    AccessoryFilter filter = AccessoryFilter.read(parser);
952                    if (clearCompatibleMatchesLocked(packageName, filter)) {
953                        changed = true;
954                    }
955                }
956                XmlUtils.nextElement(parser);
957            }
958        } catch (Exception e) {
959            Slog.w(TAG, "Unable to load component info " + aInfo.toString(), e);
960        } finally {
961            if (parser != null) parser.close();
962        }
963        return changed;
964    }
965
966    // Check to see if the package supports any USB devices or accessories.
967    // If so, clear any non-matching preferences for matching devices/accessories.
968    private void handlePackageUpdate(String packageName) {
969        synchronized (mLock) {
970            PackageInfo info;
971            boolean changed = false;
972
973            try {
974                info = mPackageManager.getPackageInfo(packageName,
975                        PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA);
976            } catch (NameNotFoundException e) {
977                Slog.e(TAG, "handlePackageUpdate could not find package " + packageName, e);
978                return;
979            }
980
981            ActivityInfo[] activities = info.activities;
982            if (activities == null) return;
983            for (int i = 0; i < activities.length; i++) {
984                // check for meta-data, both for devices and accessories
985                if (handlePackageUpdateLocked(packageName, activities[i],
986                        UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
987                    changed = true;
988                }
989                if (handlePackageUpdateLocked(packageName, activities[i],
990                        UsbManager.ACTION_USB_ACCESSORY_ATTACHED)) {
991                    changed = true;
992                }
993            }
994
995            if (changed) {
996                writeSettingsLocked();
997            }
998        }
999    }
1000
1001    public boolean hasPermission(UsbDevice device) {
1002        synchronized (mLock) {
1003            int uid = Binder.getCallingUid();
1004            if (uid == Process.SYSTEM_UID || mDisablePermissionDialogs) {
1005                return true;
1006            }
1007            SparseBooleanArray uidList = mDevicePermissionMap.get(device.getDeviceName());
1008            if (uidList == null) {
1009                return false;
1010            }
1011            return uidList.get(uid);
1012        }
1013    }
1014
1015    public boolean hasPermission(UsbAccessory accessory) {
1016        synchronized (mLock) {
1017            int uid = Binder.getCallingUid();
1018            if (uid == Process.SYSTEM_UID || mDisablePermissionDialogs) {
1019                return true;
1020            }
1021            SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory);
1022            if (uidList == null) {
1023                return false;
1024            }
1025            return uidList.get(uid);
1026        }
1027    }
1028
1029    public void checkPermission(UsbDevice device) {
1030        if (!hasPermission(device)) {
1031            throw new SecurityException("User has not given permission to device " + device);
1032        }
1033    }
1034
1035    public void checkPermission(UsbAccessory accessory) {
1036        if (!hasPermission(accessory)) {
1037            throw new SecurityException("User has not given permission to accessory " + accessory);
1038        }
1039    }
1040
1041    private void requestPermissionDialog(Intent intent, String packageName, PendingIntent pi) {
1042        final int uid = Binder.getCallingUid();
1043
1044        // compare uid with packageName to foil apps pretending to be someone else
1045        try {
1046            ApplicationInfo aInfo = mPackageManager.getApplicationInfo(packageName, 0);
1047            if (aInfo.uid != uid) {
1048                throw new IllegalArgumentException("package " + packageName +
1049                        " does not match caller's uid " + uid);
1050            }
1051        } catch (PackageManager.NameNotFoundException e) {
1052            throw new IllegalArgumentException("package " + packageName + " not found");
1053        }
1054
1055        long identity = Binder.clearCallingIdentity();
1056        intent.setClassName("com.android.systemui",
1057                "com.android.systemui.usb.UsbPermissionActivity");
1058        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1059        intent.putExtra(Intent.EXTRA_INTENT, pi);
1060        intent.putExtra("package", packageName);
1061        intent.putExtra(Intent.EXTRA_UID, uid);
1062        try {
1063            mUserContext.startActivityAsUser(intent, mUser);
1064        } catch (ActivityNotFoundException e) {
1065            Slog.e(TAG, "unable to start UsbPermissionActivity");
1066        } finally {
1067            Binder.restoreCallingIdentity(identity);
1068        }
1069    }
1070
1071    public void requestPermission(UsbDevice device, String packageName, PendingIntent pi) {
1072      Intent intent = new Intent();
1073
1074        // respond immediately if permission has already been granted
1075      if (hasPermission(device)) {
1076            intent.putExtra(UsbManager.EXTRA_DEVICE, device);
1077            intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true);
1078            try {
1079                pi.send(mUserContext, 0, intent);
1080            } catch (PendingIntent.CanceledException e) {
1081                if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled");
1082            }
1083            return;
1084        }
1085
1086        // start UsbPermissionActivity so user can choose an activity
1087        intent.putExtra(UsbManager.EXTRA_DEVICE, device);
1088        requestPermissionDialog(intent, packageName, pi);
1089    }
1090
1091    public void requestPermission(UsbAccessory accessory, String packageName, PendingIntent pi) {
1092        Intent intent = new Intent();
1093
1094        // respond immediately if permission has already been granted
1095        if (hasPermission(accessory)) {
1096            intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
1097            intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true);
1098            try {
1099                pi.send(mUserContext, 0, intent);
1100            } catch (PendingIntent.CanceledException e) {
1101                if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled");
1102            }
1103            return;
1104        }
1105
1106        intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
1107        requestPermissionDialog(intent, packageName, pi);
1108    }
1109
1110    public void setDevicePackage(UsbDevice device, String packageName) {
1111        DeviceFilter filter = new DeviceFilter(device);
1112        boolean changed = false;
1113        synchronized (mLock) {
1114            if (packageName == null) {
1115                changed = (mDevicePreferenceMap.remove(filter) != null);
1116            } else {
1117                changed = !packageName.equals(mDevicePreferenceMap.get(filter));
1118                if (changed) {
1119                    mDevicePreferenceMap.put(filter, packageName);
1120                }
1121            }
1122            if (changed) {
1123                writeSettingsLocked();
1124            }
1125        }
1126    }
1127
1128    public void setAccessoryPackage(UsbAccessory accessory, String packageName) {
1129        AccessoryFilter filter = new AccessoryFilter(accessory);
1130        boolean changed = false;
1131        synchronized (mLock) {
1132            if (packageName == null) {
1133                changed = (mAccessoryPreferenceMap.remove(filter) != null);
1134            } else {
1135                changed = !packageName.equals(mAccessoryPreferenceMap.get(filter));
1136                if (changed) {
1137                    mAccessoryPreferenceMap.put(filter, packageName);
1138                }
1139            }
1140            if (changed) {
1141                writeSettingsLocked();
1142            }
1143        }
1144    }
1145
1146    public void grantDevicePermission(UsbDevice device, int uid) {
1147        synchronized (mLock) {
1148            String deviceName = device.getDeviceName();
1149            SparseBooleanArray uidList = mDevicePermissionMap.get(deviceName);
1150            if (uidList == null) {
1151                uidList = new SparseBooleanArray(1);
1152                mDevicePermissionMap.put(deviceName, uidList);
1153            }
1154            uidList.put(uid, true);
1155        }
1156    }
1157
1158    public void grantAccessoryPermission(UsbAccessory accessory, int uid) {
1159        synchronized (mLock) {
1160            SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory);
1161            if (uidList == null) {
1162                uidList = new SparseBooleanArray(1);
1163                mAccessoryPermissionMap.put(accessory, uidList);
1164            }
1165            uidList.put(uid, true);
1166        }
1167    }
1168
1169    public boolean hasDefaults(String packageName) {
1170        synchronized (mLock) {
1171            if (mDevicePreferenceMap.values().contains(packageName)) return true;
1172            if (mAccessoryPreferenceMap.values().contains(packageName)) return true;
1173            return false;
1174        }
1175    }
1176
1177    public void clearDefaults(String packageName) {
1178        synchronized (mLock) {
1179            if (clearPackageDefaultsLocked(packageName)) {
1180                writeSettingsLocked();
1181            }
1182        }
1183    }
1184
1185    private boolean clearPackageDefaultsLocked(String packageName) {
1186        boolean cleared = false;
1187        synchronized (mLock) {
1188            if (mDevicePreferenceMap.containsValue(packageName)) {
1189                // make a copy of the key set to avoid ConcurrentModificationException
1190                Object[] keys = mDevicePreferenceMap.keySet().toArray();
1191                for (int i = 0; i < keys.length; i++) {
1192                    Object key = keys[i];
1193                    if (packageName.equals(mDevicePreferenceMap.get(key))) {
1194                        mDevicePreferenceMap.remove(key);
1195                        cleared = true;
1196                    }
1197                }
1198            }
1199            if (mAccessoryPreferenceMap.containsValue(packageName)) {
1200                // make a copy of the key set to avoid ConcurrentModificationException
1201                Object[] keys = mAccessoryPreferenceMap.keySet().toArray();
1202                for (int i = 0; i < keys.length; i++) {
1203                    Object key = keys[i];
1204                    if (packageName.equals(mAccessoryPreferenceMap.get(key))) {
1205                        mAccessoryPreferenceMap.remove(key);
1206                        cleared = true;
1207                    }
1208                }
1209            }
1210            return cleared;
1211        }
1212    }
1213
1214    public void dump(IndentingPrintWriter pw) {
1215        synchronized (mLock) {
1216            pw.println("Device permissions:");
1217            for (String deviceName : mDevicePermissionMap.keySet()) {
1218                pw.print("  " + deviceName + ": ");
1219                SparseBooleanArray uidList = mDevicePermissionMap.get(deviceName);
1220                int count = uidList.size();
1221                for (int i = 0; i < count; i++) {
1222                    pw.print(Integer.toString(uidList.keyAt(i)) + " ");
1223                }
1224                pw.println();
1225            }
1226            pw.println("Accessory permissions:");
1227            for (UsbAccessory accessory : mAccessoryPermissionMap.keySet()) {
1228                pw.print("  " + accessory + ": ");
1229                SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory);
1230                int count = uidList.size();
1231                for (int i = 0; i < count; i++) {
1232                    pw.print(Integer.toString(uidList.keyAt(i)) + " ");
1233                }
1234                pw.println();
1235            }
1236            pw.println("Device preferences:");
1237            for (DeviceFilter filter : mDevicePreferenceMap.keySet()) {
1238                pw.println("  " + filter + ": " + mDevicePreferenceMap.get(filter));
1239            }
1240            pw.println("Accessory preferences:");
1241            for (AccessoryFilter filter : mAccessoryPreferenceMap.keySet()) {
1242                pw.println("  " + filter + ": " + mAccessoryPreferenceMap.get(filter));
1243            }
1244        }
1245    }
1246
1247    private static Intent createDeviceAttachedIntent(UsbDevice device) {
1248        Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED);
1249        intent.putExtra(UsbManager.EXTRA_DEVICE, device);
1250        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1251        return intent;
1252    }
1253}
1254