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