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