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.FileUtils;
37import android.os.Process;
38import android.util.Slog;
39import android.util.SparseBooleanArray;
40import android.util.Xml;
41
42import com.android.internal.content.PackageMonitor;
43import com.android.internal.util.FastXmlSerializer;
44import com.android.internal.util.XmlUtils;
45
46import org.xmlpull.v1.XmlPullParser;
47import org.xmlpull.v1.XmlPullParserException;
48import org.xmlpull.v1.XmlSerializer;
49
50import java.io.BufferedOutputStream;
51import java.io.File;
52import java.io.FileDescriptor;
53import java.io.FileInputStream;
54import java.io.FileNotFoundException;
55import java.io.FileOutputStream;
56import java.io.IOException;
57import java.io.PrintWriter;
58import java.util.ArrayList;
59import java.util.HashMap;
60import java.util.List;
61
62class UsbSettingsManager {
63
64    private static final String TAG = "UsbSettingsManager";
65    private static final boolean DEBUG = false;
66    private static final File sSettingsFile = new File("/data/system/usb_device_manager.xml");
67
68    private final Context mContext;
69    private final PackageManager mPackageManager;
70
71    // Temporary mapping USB device name to list of UIDs with permissions for the device
72    private final HashMap<String, SparseBooleanArray> mDevicePermissionMap =
73            new HashMap<String, SparseBooleanArray>();
74    // Temporary mapping UsbAccessory to list of UIDs with permissions for the accessory
75    private final HashMap<UsbAccessory, SparseBooleanArray> mAccessoryPermissionMap =
76            new HashMap<UsbAccessory, SparseBooleanArray>();
77    // Maps DeviceFilter to user preferred application package
78    private final HashMap<DeviceFilter, String> mDevicePreferenceMap =
79            new HashMap<DeviceFilter, String>();
80    // Maps AccessoryFilter to user preferred application package
81    private final HashMap<AccessoryFilter, String> mAccessoryPreferenceMap =
82            new HashMap<AccessoryFilter, String>();
83
84    private final Object mLock = new Object();
85
86    // This class is used to describe a USB device.
87    // When used in HashMaps all values must be specified,
88    // but wildcards can be used for any of the fields in
89    // the package meta-data.
90    private static class DeviceFilter {
91        // USB Vendor ID (or -1 for unspecified)
92        public final int mVendorId;
93        // USB Product ID (or -1 for unspecified)
94        public final int mProductId;
95        // USB device or interface class (or -1 for unspecified)
96        public final int mClass;
97        // USB device subclass (or -1 for unspecified)
98        public final int mSubclass;
99        // USB device protocol (or -1 for unspecified)
100        public final int mProtocol;
101
102        public DeviceFilter(int vid, int pid, int clasz, int subclass, int protocol) {
103            mVendorId = vid;
104            mProductId = pid;
105            mClass = clasz;
106            mSubclass = subclass;
107            mProtocol = protocol;
108        }
109
110        public DeviceFilter(UsbDevice device) {
111            mVendorId = device.getVendorId();
112            mProductId = device.getProductId();
113            mClass = device.getDeviceClass();
114            mSubclass = device.getDeviceSubclass();
115            mProtocol = device.getDeviceProtocol();
116        }
117
118        public static DeviceFilter read(XmlPullParser parser)
119                throws XmlPullParserException, IOException {
120            int vendorId = -1;
121            int productId = -1;
122            int deviceClass = -1;
123            int deviceSubclass = -1;
124            int deviceProtocol = -1;
125
126            int count = parser.getAttributeCount();
127            for (int i = 0; i < count; i++) {
128                String name = parser.getAttributeName(i);
129                // All attribute values are ints
130                int value = Integer.parseInt(parser.getAttributeValue(i));
131
132                if ("vendor-id".equals(name)) {
133                    vendorId = value;
134                } else if ("product-id".equals(name)) {
135                    productId = value;
136                } else if ("class".equals(name)) {
137                    deviceClass = value;
138                } else if ("subclass".equals(name)) {
139                    deviceSubclass = value;
140                } else if ("protocol".equals(name)) {
141                    deviceProtocol = value;
142                }
143            }
144            return new DeviceFilter(vendorId, productId,
145                    deviceClass, deviceSubclass, deviceProtocol);
146        }
147
148        public void write(XmlSerializer serializer) throws IOException {
149            serializer.startTag(null, "usb-device");
150            if (mVendorId != -1) {
151                serializer.attribute(null, "vendor-id", Integer.toString(mVendorId));
152            }
153            if (mProductId != -1) {
154                serializer.attribute(null, "product-id", Integer.toString(mProductId));
155            }
156            if (mClass != -1) {
157                serializer.attribute(null, "class", Integer.toString(mClass));
158            }
159            if (mSubclass != -1) {
160                serializer.attribute(null, "subclass", Integer.toString(mSubclass));
161            }
162            if (mProtocol != -1) {
163                serializer.attribute(null, "protocol", Integer.toString(mProtocol));
164            }
165            serializer.endTag(null, "usb-device");
166        }
167
168        private boolean matches(int clasz, int subclass, int protocol) {
169            return ((mClass == -1 || clasz == mClass) &&
170                    (mSubclass == -1 || subclass == mSubclass) &&
171                    (mProtocol == -1 || protocol == mProtocol));
172        }
173
174        public boolean matches(UsbDevice device) {
175            if (mVendorId != -1 && device.getVendorId() != mVendorId) return false;
176            if (mProductId != -1 && device.getProductId() != mProductId) return false;
177
178            // check device class/subclass/protocol
179            if (matches(device.getDeviceClass(), device.getDeviceSubclass(),
180                    device.getDeviceProtocol())) return true;
181
182            // if device doesn't match, check the interfaces
183            int count = device.getInterfaceCount();
184            for (int i = 0; i < count; i++) {
185                UsbInterface intf = device.getInterface(i);
186                 if (matches(intf.getInterfaceClass(), intf.getInterfaceSubclass(),
187                        intf.getInterfaceProtocol())) return true;
188            }
189
190            return false;
191        }
192
193        public boolean matches(DeviceFilter f) {
194            if (mVendorId != -1 && f.mVendorId != mVendorId) return false;
195            if (mProductId != -1 && f.mProductId != mProductId) return false;
196
197            // check device class/subclass/protocol
198            return matches(f.mClass, f.mSubclass, f.mProtocol);
199        }
200
201        @Override
202        public boolean equals(Object obj) {
203            // can't compare if we have wildcard strings
204            if (mVendorId == -1 || mProductId == -1 ||
205                    mClass == -1 || mSubclass == -1 || mProtocol == -1) {
206                return false;
207            }
208            if (obj instanceof DeviceFilter) {
209                DeviceFilter filter = (DeviceFilter)obj;
210                return (filter.mVendorId == mVendorId &&
211                        filter.mProductId == mProductId &&
212                        filter.mClass == mClass &&
213                        filter.mSubclass == mSubclass &&
214                        filter.mProtocol == mProtocol);
215            }
216            if (obj instanceof UsbDevice) {
217                UsbDevice device = (UsbDevice)obj;
218                return (device.getVendorId() == mVendorId &&
219                        device.getProductId() == mProductId &&
220                        device.getDeviceClass() == mClass &&
221                        device.getDeviceSubclass() == mSubclass &&
222                        device.getDeviceProtocol() == mProtocol);
223            }
224            return false;
225        }
226
227        @Override
228        public int hashCode() {
229            return (((mVendorId << 16) | mProductId) ^
230                    ((mClass << 16) | (mSubclass << 8) | mProtocol));
231        }
232
233        @Override
234        public String toString() {
235            return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + mProductId +
236                    ",mClass=" + mClass + ",mSubclass=" + mSubclass +
237                    ",mProtocol=" + mProtocol + "]";
238        }
239    }
240
241    // This class is used to describe a USB accessory.
242    // When used in HashMaps all values must be specified,
243    // but wildcards can be used for any of the fields in
244    // the package meta-data.
245    private static class AccessoryFilter {
246        // USB accessory manufacturer (or null for unspecified)
247        public final String mManufacturer;
248        // USB accessory model (or null for unspecified)
249        public final String mModel;
250        // USB accessory version (or null for unspecified)
251        public final String mVersion;
252
253        public AccessoryFilter(String manufacturer, String model, String version) {
254            mManufacturer = manufacturer;
255            mModel = model;
256            mVersion = version;
257        }
258
259        public AccessoryFilter(UsbAccessory accessory) {
260            mManufacturer = accessory.getManufacturer();
261            mModel = accessory.getModel();
262            mVersion = accessory.getVersion();
263        }
264
265        public static AccessoryFilter read(XmlPullParser parser)
266                throws XmlPullParserException, IOException {
267            String manufacturer = null;
268            String model = null;
269            String version = null;
270
271            int count = parser.getAttributeCount();
272            for (int i = 0; i < count; i++) {
273                String name = parser.getAttributeName(i);
274                String value = parser.getAttributeValue(i);
275
276                if ("manufacturer".equals(name)) {
277                    manufacturer = value;
278                } else if ("model".equals(name)) {
279                    model = value;
280                } else if ("version".equals(name)) {
281                    version = value;
282                }
283             }
284             return new AccessoryFilter(manufacturer, model, version);
285        }
286
287        public void write(XmlSerializer serializer)throws IOException {
288            serializer.startTag(null, "usb-accessory");
289            if (mManufacturer != null) {
290                serializer.attribute(null, "manufacturer", mManufacturer);
291            }
292            if (mModel != null) {
293                serializer.attribute(null, "model", mModel);
294            }
295            if (mVersion != null) {
296                serializer.attribute(null, "version", mVersion);
297            }
298            serializer.endTag(null, "usb-accessory");
299        }
300
301        public boolean matches(UsbAccessory acc) {
302            if (mManufacturer != null && !acc.getManufacturer().equals(mManufacturer)) return false;
303            if (mModel != null && !acc.getModel().equals(mModel)) return false;
304            if (mVersion != null && !acc.getVersion().equals(mVersion)) return false;
305            return true;
306        }
307
308        public boolean matches(AccessoryFilter f) {
309            if (mManufacturer != null && !f.mManufacturer.equals(mManufacturer)) return false;
310            if (mModel != null && !f.mModel.equals(mModel)) return false;
311            if (mVersion != null && !f.mVersion.equals(mVersion)) return false;
312            return true;
313        }
314
315        @Override
316        public boolean equals(Object obj) {
317            // can't compare if we have wildcard strings
318            if (mManufacturer == null || mModel == null || mVersion == null) {
319                return false;
320            }
321            if (obj instanceof AccessoryFilter) {
322                AccessoryFilter filter = (AccessoryFilter)obj;
323                return (mManufacturer.equals(filter.mManufacturer) &&
324                        mModel.equals(filter.mModel) &&
325                        mVersion.equals(filter.mVersion));
326            }
327            if (obj instanceof UsbAccessory) {
328                UsbAccessory accessory = (UsbAccessory)obj;
329                return (mManufacturer.equals(accessory.getManufacturer()) &&
330                        mModel.equals(accessory.getModel()) &&
331                        mVersion.equals(accessory.getVersion()));
332            }
333            return false;
334        }
335
336        @Override
337        public int hashCode() {
338            return ((mManufacturer == null ? 0 : mManufacturer.hashCode()) ^
339                    (mModel == null ? 0 : mModel.hashCode()) ^
340                    (mVersion == null ? 0 : mVersion.hashCode()));
341        }
342
343        @Override
344        public String toString() {
345            return "AccessoryFilter[mManufacturer=\"" + mManufacturer +
346                                "\", mModel=\"" + mModel +
347                                "\", mVersion=\"" + mVersion + "\"]";
348        }
349    }
350
351    private class MyPackageMonitor extends PackageMonitor {
352
353        public void onPackageAdded(String packageName, int uid) {
354            handlePackageUpdate(packageName);
355        }
356
357        public void onPackageChanged(String packageName, int uid, String[] components) {
358            handlePackageUpdate(packageName);
359        }
360
361        public void onPackageRemoved(String packageName, int uid) {
362            clearDefaults(packageName);
363        }
364    }
365    MyPackageMonitor mPackageMonitor = new MyPackageMonitor();
366
367    public UsbSettingsManager(Context context) {
368        mContext = context;
369        mPackageManager = context.getPackageManager();
370        synchronized (mLock) {
371            readSettingsLocked();
372        }
373        mPackageMonitor.register(context, null, true);
374    }
375
376    private void readPreference(XmlPullParser parser)
377            throws XmlPullParserException, IOException {
378        String packageName = null;
379        int count = parser.getAttributeCount();
380        for (int i = 0; i < count; i++) {
381            if ("package".equals(parser.getAttributeName(i))) {
382                packageName = parser.getAttributeValue(i);
383                break;
384            }
385        }
386        XmlUtils.nextElement(parser);
387        if ("usb-device".equals(parser.getName())) {
388            DeviceFilter filter = DeviceFilter.read(parser);
389            mDevicePreferenceMap.put(filter, packageName);
390        } else if ("usb-accessory".equals(parser.getName())) {
391            AccessoryFilter filter = AccessoryFilter.read(parser);
392            mAccessoryPreferenceMap.put(filter, packageName);
393        }
394        XmlUtils.nextElement(parser);
395    }
396
397    private void readSettingsLocked() {
398        FileInputStream stream = null;
399        try {
400            stream = new FileInputStream(sSettingsFile);
401            XmlPullParser parser = Xml.newPullParser();
402            parser.setInput(stream, null);
403
404            XmlUtils.nextElement(parser);
405            while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
406                String tagName = parser.getName();
407                if ("preference".equals(tagName)) {
408                    readPreference(parser);
409                 } else {
410                    XmlUtils.nextElement(parser);
411                }
412            }
413        } catch (FileNotFoundException e) {
414            if (DEBUG) Slog.d(TAG, "settings file not found");
415        } catch (Exception e) {
416            Slog.e(TAG, "error reading settings file, deleting to start fresh", e);
417            sSettingsFile.delete();
418        } finally {
419            if (stream != null) {
420                try {
421                    stream.close();
422                } catch (IOException e) {
423                }
424            }
425        }
426    }
427
428    private void writeSettingsLocked() {
429        FileOutputStream fos = null;
430        try {
431            FileOutputStream fstr = new FileOutputStream(sSettingsFile);
432            if (DEBUG) Slog.d(TAG, "writing settings to " + fstr);
433            BufferedOutputStream str = new BufferedOutputStream(fstr);
434            FastXmlSerializer serializer = new FastXmlSerializer();
435            serializer.setOutput(str, "utf-8");
436            serializer.startDocument(null, true);
437            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
438            serializer.startTag(null, "settings");
439
440            for (DeviceFilter filter : mDevicePreferenceMap.keySet()) {
441                serializer.startTag(null, "preference");
442                serializer.attribute(null, "package", mDevicePreferenceMap.get(filter));
443                filter.write(serializer);
444                serializer.endTag(null, "preference");
445            }
446
447            for (AccessoryFilter filter : mAccessoryPreferenceMap.keySet()) {
448                serializer.startTag(null, "preference");
449                serializer.attribute(null, "package", mAccessoryPreferenceMap.get(filter));
450                filter.write(serializer);
451                serializer.endTag(null, "preference");
452            }
453
454            serializer.endTag(null, "settings");
455            serializer.endDocument();
456
457            str.flush();
458            FileUtils.sync(fstr);
459            str.close();
460        } catch (Exception e) {
461            Slog.e(TAG, "error writing settings file, deleting to start fresh", e);
462            sSettingsFile.delete();
463        }
464    }
465
466    // Checks to see if a package matches a device or accessory.
467    // Only one of device and accessory should be non-null.
468    private boolean packageMatchesLocked(ResolveInfo info, String metaDataName,
469            UsbDevice device, UsbAccessory accessory) {
470        ActivityInfo ai = info.activityInfo;
471
472        XmlResourceParser parser = null;
473        try {
474            parser = ai.loadXmlMetaData(mPackageManager, metaDataName);
475            if (parser == null) {
476                Slog.w(TAG, "no meta-data for " + info);
477                return false;
478            }
479
480            XmlUtils.nextElement(parser);
481            while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
482                String tagName = parser.getName();
483                if (device != null && "usb-device".equals(tagName)) {
484                    DeviceFilter filter = DeviceFilter.read(parser);
485                    if (filter.matches(device)) {
486                        return true;
487                    }
488                }
489                else if (accessory != null && "usb-accessory".equals(tagName)) {
490                    AccessoryFilter filter = AccessoryFilter.read(parser);
491                    if (filter.matches(accessory)) {
492                        return true;
493                    }
494                }
495                XmlUtils.nextElement(parser);
496            }
497        } catch (Exception e) {
498            Slog.w(TAG, "Unable to load component info " + info.toString(), e);
499        } finally {
500            if (parser != null) parser.close();
501        }
502        return false;
503    }
504
505    private final ArrayList<ResolveInfo> getDeviceMatchesLocked(UsbDevice device, Intent intent) {
506        ArrayList<ResolveInfo> matches = new ArrayList<ResolveInfo>();
507        List<ResolveInfo> resolveInfos = mPackageManager.queryIntentActivities(intent,
508                PackageManager.GET_META_DATA);
509        int count = resolveInfos.size();
510        for (int i = 0; i < count; i++) {
511            ResolveInfo resolveInfo = resolveInfos.get(i);
512            if (packageMatchesLocked(resolveInfo, intent.getAction(), device, null)) {
513                matches.add(resolveInfo);
514            }
515        }
516        return matches;
517    }
518
519    private final ArrayList<ResolveInfo> getAccessoryMatchesLocked(
520            UsbAccessory accessory, Intent intent) {
521        ArrayList<ResolveInfo> matches = new ArrayList<ResolveInfo>();
522        List<ResolveInfo> resolveInfos = mPackageManager.queryIntentActivities(intent,
523                PackageManager.GET_META_DATA);
524        int count = resolveInfos.size();
525        for (int i = 0; i < count; i++) {
526            ResolveInfo resolveInfo = resolveInfos.get(i);
527            if (packageMatchesLocked(resolveInfo, intent.getAction(), null, accessory)) {
528                matches.add(resolveInfo);
529            }
530        }
531        return matches;
532    }
533
534    public void deviceAttached(UsbDevice device) {
535        Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED);
536        intent.putExtra(UsbManager.EXTRA_DEVICE, device);
537        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
538
539        ArrayList<ResolveInfo> matches;
540        String defaultPackage;
541        synchronized (mLock) {
542            matches = getDeviceMatchesLocked(device, intent);
543            // Launch our default activity directly, if we have one.
544            // Otherwise we will start the UsbResolverActivity to allow the user to choose.
545            defaultPackage = mDevicePreferenceMap.get(new DeviceFilter(device));
546        }
547
548        resolveActivity(intent, matches, defaultPackage, device, null);
549    }
550
551    public void deviceDetached(UsbDevice device) {
552        // clear temporary permissions for the device
553        mDevicePermissionMap.remove(device.getDeviceName());
554
555        Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_DETACHED);
556        intent.putExtra(UsbManager.EXTRA_DEVICE, device);
557        if (DEBUG) Slog.d(TAG, "usbDeviceRemoved, sending " + intent);
558        mContext.sendBroadcast(intent);
559    }
560
561    public void accessoryAttached(UsbAccessory accessory) {
562        Intent intent = new Intent(UsbManager.ACTION_USB_ACCESSORY_ATTACHED);
563        intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
564        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
565
566        ArrayList<ResolveInfo> matches;
567        String defaultPackage;
568        synchronized (mLock) {
569            matches = getAccessoryMatchesLocked(accessory, intent);
570            // Launch our default activity directly, if we have one.
571            // Otherwise we will start the UsbResolverActivity to allow the user to choose.
572            defaultPackage = mAccessoryPreferenceMap.get(new AccessoryFilter(accessory));
573        }
574
575        resolveActivity(intent, matches, defaultPackage, null, accessory);
576    }
577
578    public void accessoryDetached(UsbAccessory accessory) {
579        // clear temporary permissions for the accessory
580        mAccessoryPermissionMap.remove(accessory);
581
582        Intent intent = new Intent(
583                UsbManager.ACTION_USB_ACCESSORY_DETACHED);
584        intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
585        mContext.sendBroadcast(intent);
586    }
587
588    private void resolveActivity(Intent intent, ArrayList<ResolveInfo> matches,
589            String defaultPackage, UsbDevice device, UsbAccessory accessory) {
590        int count = matches.size();
591
592        // don't show the resolver activity if there are no choices available
593        if (count == 0) {
594            if (accessory != null) {
595                String uri = accessory.getUri();
596                if (uri != null && uri.length() > 0) {
597                    // display URI to user
598                    // start UsbResolverActivity so user can choose an activity
599                    Intent dialogIntent = new Intent();
600                    dialogIntent.setClassName("com.android.systemui",
601                            "com.android.systemui.usb.UsbAccessoryUriActivity");
602                    dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
603                    dialogIntent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
604                    dialogIntent.putExtra("uri", uri);
605                    try {
606                        mContext.startActivity(dialogIntent);
607                    } catch (ActivityNotFoundException e) {
608                        Slog.e(TAG, "unable to start UsbAccessoryUriActivity");
609                    }
610                }
611            }
612
613            // do nothing
614            return;
615        }
616
617        ResolveInfo defaultRI = null;
618        if (count == 1 && defaultPackage == null) {
619            // Check to see if our single choice is on the system partition.
620            // If so, treat it as our default without calling UsbResolverActivity
621            ResolveInfo rInfo = matches.get(0);
622            if (rInfo.activityInfo != null &&
623                    rInfo.activityInfo.applicationInfo != null &&
624                    (rInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
625                defaultRI = rInfo;
626            }
627        }
628
629        if (defaultRI == null && defaultPackage != null) {
630            // look for default activity
631            for (int i = 0; i < count; i++) {
632                ResolveInfo rInfo = matches.get(i);
633                if (rInfo.activityInfo != null &&
634                        defaultPackage.equals(rInfo.activityInfo.packageName)) {
635                    defaultRI = rInfo;
636                    break;
637                }
638            }
639        }
640
641        if (defaultRI != null) {
642            // grant permission for default activity
643            if (device != null) {
644                grantDevicePermission(device, defaultRI.activityInfo.applicationInfo.uid);
645            } else if (accessory != null) {
646                grantAccessoryPermission(accessory, defaultRI.activityInfo.applicationInfo.uid);
647            }
648
649            // start default activity directly
650            try {
651                intent.setComponent(
652                        new ComponentName(defaultRI.activityInfo.packageName,
653                                defaultRI.activityInfo.name));
654                mContext.startActivity(intent);
655            } catch (ActivityNotFoundException e) {
656                Slog.e(TAG, "startActivity failed", e);
657            }
658        } else {
659            Intent resolverIntent = new Intent();
660            resolverIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
661
662            if (count == 1) {
663                // start UsbConfirmActivity if there is only one choice
664                resolverIntent.setClassName("com.android.systemui",
665                        "com.android.systemui.usb.UsbConfirmActivity");
666                resolverIntent.putExtra("rinfo", matches.get(0));
667
668                if (device != null) {
669                    resolverIntent.putExtra(UsbManager.EXTRA_DEVICE, device);
670                } else {
671                    resolverIntent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
672                }
673            } else {
674                // start UsbResolverActivity so user can choose an activity
675                resolverIntent.setClassName("com.android.systemui",
676                        "com.android.systemui.usb.UsbResolverActivity");
677                resolverIntent.putParcelableArrayListExtra("rlist", matches);
678                resolverIntent.putExtra(Intent.EXTRA_INTENT, intent);
679            }
680            try {
681                mContext.startActivity(resolverIntent);
682            } catch (ActivityNotFoundException e) {
683                Slog.e(TAG, "unable to start activity " + resolverIntent);
684            }
685        }
686    }
687
688    private boolean clearCompatibleMatchesLocked(String packageName, DeviceFilter filter) {
689        boolean changed = false;
690        for (DeviceFilter test : mDevicePreferenceMap.keySet()) {
691            if (filter.matches(test)) {
692                mDevicePreferenceMap.remove(test);
693                changed = true;
694            }
695        }
696        return changed;
697    }
698
699    private boolean clearCompatibleMatchesLocked(String packageName, AccessoryFilter filter) {
700        boolean changed = false;
701        for (AccessoryFilter test : mAccessoryPreferenceMap.keySet()) {
702            if (filter.matches(test)) {
703                mAccessoryPreferenceMap.remove(test);
704                changed = true;
705            }
706        }
707        return changed;
708    }
709
710    private boolean handlePackageUpdateLocked(String packageName, ActivityInfo aInfo,
711            String metaDataName) {
712        XmlResourceParser parser = null;
713        boolean changed = false;
714
715        try {
716            parser = aInfo.loadXmlMetaData(mPackageManager, metaDataName);
717            if (parser == null) return false;
718
719            XmlUtils.nextElement(parser);
720            while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
721                String tagName = parser.getName();
722                if ("usb-device".equals(tagName)) {
723                    DeviceFilter filter = DeviceFilter.read(parser);
724                    if (clearCompatibleMatchesLocked(packageName, filter)) {
725                        changed = true;
726                    }
727                }
728                else if ("usb-accessory".equals(tagName)) {
729                    AccessoryFilter filter = AccessoryFilter.read(parser);
730                    if (clearCompatibleMatchesLocked(packageName, filter)) {
731                        changed = true;
732                    }
733                }
734                XmlUtils.nextElement(parser);
735            }
736        } catch (Exception e) {
737            Slog.w(TAG, "Unable to load component info " + aInfo.toString(), e);
738        } finally {
739            if (parser != null) parser.close();
740        }
741        return changed;
742    }
743
744    // Check to see if the package supports any USB devices or accessories.
745    // If so, clear any non-matching preferences for matching devices/accessories.
746    private void handlePackageUpdate(String packageName) {
747        synchronized (mLock) {
748            PackageInfo info;
749            boolean changed = false;
750
751            try {
752                info = mPackageManager.getPackageInfo(packageName,
753                        PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA);
754            } catch (NameNotFoundException e) {
755                Slog.e(TAG, "handlePackageUpdate could not find package " + packageName, e);
756                return;
757            }
758
759            ActivityInfo[] activities = info.activities;
760            if (activities == null) return;
761            for (int i = 0; i < activities.length; i++) {
762                // check for meta-data, both for devices and accessories
763                if (handlePackageUpdateLocked(packageName, activities[i],
764                        UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
765                    changed = true;
766                }
767                if (handlePackageUpdateLocked(packageName, activities[i],
768                        UsbManager.ACTION_USB_ACCESSORY_ATTACHED)) {
769                    changed = true;
770                }
771            }
772
773            if (changed) {
774                writeSettingsLocked();
775            }
776        }
777    }
778
779    public boolean hasPermission(UsbDevice device) {
780        synchronized (mLock) {
781            SparseBooleanArray uidList = mDevicePermissionMap.get(device.getDeviceName());
782            if (uidList == null) {
783                return false;
784            }
785            return uidList.get(Binder.getCallingUid());
786        }
787    }
788
789    public boolean hasPermission(UsbAccessory accessory) {
790        synchronized (mLock) {
791            SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory);
792            if (uidList == null) {
793                return false;
794            }
795            return uidList.get(Binder.getCallingUid());
796        }
797    }
798
799    public void checkPermission(UsbDevice device) {
800        if (!hasPermission(device)) {
801            throw new SecurityException("User has not given permission to device " + device);
802        }
803    }
804
805    public void checkPermission(UsbAccessory accessory) {
806        if (!hasPermission(accessory)) {
807            throw new SecurityException("User has not given permission to accessory " + accessory);
808        }
809    }
810
811    private void requestPermissionDialog(Intent intent, String packageName, PendingIntent pi) {
812        int uid = Binder.getCallingUid();
813
814        // compare uid with packageName to foil apps pretending to be someone else
815        try {
816            ApplicationInfo aInfo = mPackageManager.getApplicationInfo(packageName, 0);
817            if (aInfo.uid != uid) {
818                throw new IllegalArgumentException("package " + packageName +
819                        " does not match caller's uid " + uid);
820            }
821        } catch (PackageManager.NameNotFoundException e) {
822            throw new IllegalArgumentException("package " + packageName + " not found");
823        }
824
825        long identity = Binder.clearCallingIdentity();
826        intent.setClassName("com.android.systemui",
827                "com.android.systemui.usb.UsbPermissionActivity");
828        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
829        intent.putExtra(Intent.EXTRA_INTENT, pi);
830        intent.putExtra("package", packageName);
831        intent.putExtra("uid", uid);
832        try {
833            mContext.startActivity(intent);
834        } catch (ActivityNotFoundException e) {
835            Slog.e(TAG, "unable to start UsbPermissionActivity");
836        } finally {
837            Binder.restoreCallingIdentity(identity);
838        }
839    }
840
841    public void requestPermission(UsbDevice device, String packageName, PendingIntent pi) {
842      Intent intent = new Intent();
843
844        // respond immediately if permission has already been granted
845      if (hasPermission(device)) {
846            intent.putExtra(UsbManager.EXTRA_DEVICE, device);
847            intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true);
848            try {
849                pi.send(mContext, 0, intent);
850            } catch (PendingIntent.CanceledException e) {
851                if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled");
852            }
853            return;
854        }
855
856        // start UsbPermissionActivity so user can choose an activity
857        intent.putExtra(UsbManager.EXTRA_DEVICE, device);
858        requestPermissionDialog(intent, packageName, pi);
859    }
860
861    public void requestPermission(UsbAccessory accessory, String packageName, PendingIntent pi) {
862      Intent intent = new Intent();
863
864        // respond immediately if permission has already been granted
865        if (hasPermission(accessory)) {
866            intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
867            intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true);
868           try {
869                pi.send(mContext, 0, intent);
870            } catch (PendingIntent.CanceledException e) {
871                if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled");
872            }
873            return;
874        }
875
876        intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
877        requestPermissionDialog(intent, packageName, pi);
878    }
879
880    public void setDevicePackage(UsbDevice device, String packageName) {
881        DeviceFilter filter = new DeviceFilter(device);
882        boolean changed = false;
883        synchronized (mLock) {
884            if (packageName == null) {
885                changed = (mDevicePreferenceMap.remove(filter) != null);
886            } else {
887                changed = !packageName.equals(mDevicePreferenceMap.get(filter));
888                if (changed) {
889                    mDevicePreferenceMap.put(filter, packageName);
890                }
891            }
892            if (changed) {
893                writeSettingsLocked();
894            }
895        }
896    }
897
898    public void setAccessoryPackage(UsbAccessory accessory, String packageName) {
899        AccessoryFilter filter = new AccessoryFilter(accessory);
900        boolean changed = false;
901        synchronized (mLock) {
902            if (packageName == null) {
903                changed = (mAccessoryPreferenceMap.remove(filter) != null);
904            } else {
905                changed = !packageName.equals(mAccessoryPreferenceMap.get(filter));
906                if (changed) {
907                    mAccessoryPreferenceMap.put(filter, packageName);
908                }
909            }
910            if (changed) {
911                writeSettingsLocked();
912            }
913        }
914    }
915
916    public void grantDevicePermission(UsbDevice device, int uid) {
917        synchronized (mLock) {
918            String deviceName = device.getDeviceName();
919            SparseBooleanArray uidList = mDevicePermissionMap.get(deviceName);
920            if (uidList == null) {
921                uidList = new SparseBooleanArray(1);
922                mDevicePermissionMap.put(deviceName, uidList);
923            }
924            uidList.put(uid, true);
925        }
926    }
927
928    public void grantAccessoryPermission(UsbAccessory accessory, int uid) {
929        synchronized (mLock) {
930            SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory);
931            if (uidList == null) {
932                uidList = new SparseBooleanArray(1);
933                mAccessoryPermissionMap.put(accessory, uidList);
934            }
935            uidList.put(uid, true);
936        }
937    }
938
939    public boolean hasDefaults(String packageName) {
940        synchronized (mLock) {
941            if (mDevicePreferenceMap.values().contains(packageName)) return true;
942            if (mAccessoryPreferenceMap.values().contains(packageName)) return true;
943            return false;
944        }
945    }
946
947    public void clearDefaults(String packageName) {
948        synchronized (mLock) {
949            if (clearPackageDefaultsLocked(packageName)) {
950                writeSettingsLocked();
951            }
952        }
953    }
954
955    private boolean clearPackageDefaultsLocked(String packageName) {
956        boolean cleared = false;
957        synchronized (mLock) {
958            if (mDevicePreferenceMap.containsValue(packageName)) {
959                // make a copy of the key set to avoid ConcurrentModificationException
960                Object[] keys = mDevicePreferenceMap.keySet().toArray();
961                for (int i = 0; i < keys.length; i++) {
962                    Object key = keys[i];
963                    if (packageName.equals(mDevicePreferenceMap.get(key))) {
964                        mDevicePreferenceMap.remove(key);
965                        cleared = true;
966                    }
967                }
968            }
969            if (mAccessoryPreferenceMap.containsValue(packageName)) {
970                // make a copy of the key set to avoid ConcurrentModificationException
971                Object[] keys = mAccessoryPreferenceMap.keySet().toArray();
972                for (int i = 0; i < keys.length; i++) {
973                    Object key = keys[i];
974                    if (packageName.equals(mAccessoryPreferenceMap.get(key))) {
975                        mAccessoryPreferenceMap.remove(key);
976                        cleared = true;
977                    }
978                }
979            }
980            return cleared;
981        }
982    }
983
984    public void dump(FileDescriptor fd, PrintWriter pw) {
985        synchronized (mLock) {
986            pw.println("  Device permissions:");
987            for (String deviceName : mDevicePermissionMap.keySet()) {
988                pw.print("    " + deviceName + ": ");
989                SparseBooleanArray uidList = mDevicePermissionMap.get(deviceName);
990                int count = uidList.size();
991                for (int i = 0; i < count; i++) {
992                    pw.print(Integer.toString(uidList.keyAt(i)) + " ");
993                }
994                pw.println("");
995            }
996            pw.println("  Accessory permissions:");
997            for (UsbAccessory accessory : mAccessoryPermissionMap.keySet()) {
998                pw.print("    " + accessory + ": ");
999                SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory);
1000                int count = uidList.size();
1001                for (int i = 0; i < count; i++) {
1002                    pw.print(Integer.toString(uidList.keyAt(i)) + " ");
1003                }
1004                pw.println("");
1005            }
1006            pw.println("  Device preferences:");
1007            for (DeviceFilter filter : mDevicePreferenceMap.keySet()) {
1008                pw.println("    " + filter + ": " + mDevicePreferenceMap.get(filter));
1009            }
1010            pw.println("  Accessory preferences:");
1011            for (AccessoryFilter filter : mAccessoryPreferenceMap.keySet()) {
1012                pw.println("    " + filter + ": " + mAccessoryPreferenceMap.get(filter));
1013            }
1014        }
1015    }
1016}
1017