UsbDeviceSettingsManager.java revision 611af238185cf924a425a1a2154b8439b8f8d7a5
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.content.ActivityNotFoundException;
20import android.content.ComponentName;
21import android.content.Context;
22import android.content.Intent;
23import android.content.pm.ActivityInfo;
24import android.content.pm.PackageManager;
25import android.content.pm.PackageManager.NameNotFoundException;
26import android.content.pm.ResolveInfo;
27import android.content.res.XmlResourceParser;
28import android.hardware.usb.UsbAccessory;
29import android.hardware.usb.UsbManager;
30import android.os.Binder;
31import android.os.FileUtils;
32import android.os.Process;
33import android.util.Log;
34import android.util.SparseArray;
35import android.util.Xml;
36
37import com.android.internal.content.PackageMonitor;
38import com.android.internal.util.FastXmlSerializer;
39import com.android.internal.util.XmlUtils;
40
41import org.xmlpull.v1.XmlPullParser;
42import org.xmlpull.v1.XmlPullParserException;
43import org.xmlpull.v1.XmlSerializer;
44
45import java.io.BufferedOutputStream;
46import java.io.File;
47import java.io.FileDescriptor;
48import java.io.FileInputStream;
49import java.io.FileNotFoundException;
50import java.io.FileOutputStream;
51import java.io.IOException;
52import java.io.PrintWriter;
53import java.util.ArrayList;
54import java.util.HashMap;
55import java.util.List;
56
57class UsbDeviceSettingsManager {
58
59    private static final String TAG = "UsbDeviceSettingsManager";
60    private static final File sSettingsFile = new File("/data/system/usb_device_manager.xml");
61
62    private final Context mContext;
63
64    // maps UID to user approved USB accessories
65    private final SparseArray<ArrayList<AccessoryFilter>> mAccessoryPermissionMap =
66            new SparseArray<ArrayList<AccessoryFilter>>();
67    // Maps AccessoryFilter to user preferred application package
68    private final HashMap<AccessoryFilter, String> mAccessoryPreferenceMap =
69            new HashMap<AccessoryFilter, String>();
70
71    private final Object mLock = new Object();
72
73    // This class is used to describe a USB accessory.
74    // When used in HashMaps all values must be specified,
75    // but wildcards can be used for any of the fields in
76    // the package meta-data.
77    private static class AccessoryFilter {
78        // USB accessory manufacturer (or null for unspecified)
79        public final String mManufacturer;
80        // USB accessory model (or null for unspecified)
81        public final String mModel;
82        // USB accessory type (or null for unspecified)
83        public final String mType;
84        // USB accessory version (or null for unspecified)
85        public final String mVersion;
86
87        public AccessoryFilter(String manufacturer, String model, String type, String version) {
88            mManufacturer = manufacturer;
89            mModel = model;
90            mType = type;
91            mVersion = version;
92        }
93
94        public AccessoryFilter(UsbAccessory accessory) {
95            mManufacturer = accessory.getManufacturer();
96            mModel = accessory.getModel();
97            mType = accessory.getType();
98            mVersion = accessory.getVersion();
99        }
100
101        public static AccessoryFilter read(XmlPullParser parser)
102                throws XmlPullParserException, IOException {
103            String manufacturer = null;
104            String model = null;
105            String type = null;
106            String version = null;
107
108            int count = parser.getAttributeCount();
109            for (int i = 0; i < count; i++) {
110                String name = parser.getAttributeName(i);
111                String value = parser.getAttributeValue(i);
112
113                if ("manufacturer".equals(name)) {
114                    manufacturer = value;
115                } else if ("model".equals(name)) {
116                    model = value;
117                } else if ("type".equals(name)) {
118                    type = value;
119                } else if ("version".equals(name)) {
120                    version = value;
121                }
122             }
123             return new AccessoryFilter(manufacturer, model, type, version);
124        }
125
126        public void write(XmlSerializer serializer)throws IOException {
127            serializer.startTag(null, "usb-accessory");
128            if (mManufacturer != null) {
129                serializer.attribute(null, "manufacturer", mManufacturer);
130            }
131            if (mModel != null) {
132                serializer.attribute(null, "model", mModel);
133            }
134            if (mType != null) {
135                serializer.attribute(null, "type", mType);
136            }
137            if (mVersion != null) {
138                serializer.attribute(null, "version", mVersion);
139            }
140            serializer.endTag(null, "usb-accessory");
141        }
142
143        public boolean matches(UsbAccessory acc) {
144            if (mManufacturer != null && !acc.getManufacturer().equals(mManufacturer)) return false;
145            if (mModel != null && !acc.getModel().equals(mModel)) return false;
146            if (mType != null && !acc.getType().equals(mType)) return false;
147            if (mVersion != null && !acc.getVersion().equals(mVersion)) return false;
148            return true;
149        }
150
151        @Override
152        public boolean equals(Object obj) {
153            // can't compare if we have wildcard strings
154            if (mManufacturer == null || mModel == null || mType == null || mVersion == null) {
155                return false;
156            }
157            if (obj instanceof AccessoryFilter) {
158                AccessoryFilter filter = (AccessoryFilter)obj;
159                return (mManufacturer.equals(filter.mManufacturer) &&
160                        mModel.equals(filter.mModel) &&
161                        mType.equals(filter.mType) &&
162                        mVersion.equals(filter.mVersion));
163            }
164            if (obj instanceof UsbAccessory) {
165                UsbAccessory accessory = (UsbAccessory)obj;
166                return (mManufacturer.equals(accessory.getManufacturer()) &&
167                        mModel.equals(accessory.getModel()) &&
168                        mType.equals(accessory.getType()) &&
169                        mVersion.equals(accessory.getVersion()));
170            }
171            return false;
172        }
173
174        @Override
175        public int hashCode() {
176            return ((mManufacturer == null ? 0 : mManufacturer.hashCode()) ^
177                    (mModel == null ? 0 : mModel.hashCode()) ^
178                    (mType == null ? 0 : mType.hashCode()) ^
179                    (mVersion == null ? 0 : mVersion.hashCode()));
180        }
181
182        @Override
183        public String toString() {
184            return "AccessoryFilter[mManufacturer=\"" + mManufacturer +
185                                "\", mModel=\"" + mModel +
186                                "\", mType=\"" + mType +
187                                "\", mVersion=\"" + mVersion + "\"]";
188        }
189    }
190
191    private class MyPackageMonitor extends PackageMonitor {
192        public void onPackageRemoved(String packageName, int uid) {
193            synchronized (mLock) {
194                // clear all activity preferences for the package
195                if (clearPackageDefaultsLocked(packageName)) {
196                    writeSettingsLocked();
197                }
198            }
199        }
200
201        public void onUidRemoved(int uid) {
202            synchronized (mLock) {
203                // clear all permissions for the UID
204                if (clearUidDefaultsLocked(uid)) {
205                    writeSettingsLocked();
206                }
207            }
208        }
209    }
210    MyPackageMonitor mPackageMonitor = new MyPackageMonitor();
211
212    public UsbDeviceSettingsManager(Context context) {
213        mContext = context;
214        synchronized (mLock) {
215            readSettingsLocked();
216        }
217        mPackageMonitor.register(context, true);
218    }
219
220    private void readAccessoryPermission(XmlPullParser parser)
221            throws XmlPullParserException, IOException {
222        int uid = -1;
223        ArrayList<AccessoryFilter> filters = new ArrayList<AccessoryFilter>();
224        int count = parser.getAttributeCount();
225        for (int i = 0; i < count; i++) {
226            if ("uid".equals(parser.getAttributeName(i))) {
227                uid = Integer.parseInt(parser.getAttributeValue(i));
228                break;
229            }
230        }
231        XmlUtils.nextElement(parser);
232        while ("usb-accessory".equals(parser.getName())) {
233            filters.add(AccessoryFilter.read(parser));
234            XmlUtils.nextElement(parser);
235        }
236        mAccessoryPermissionMap.put(uid, filters);
237    }
238
239    private void readPreference(XmlPullParser parser)
240            throws XmlPullParserException, IOException {
241        String packageName = null;
242        int count = parser.getAttributeCount();
243        for (int i = 0; i < count; i++) {
244            if ("package".equals(parser.getAttributeName(i))) {
245                packageName = parser.getAttributeValue(i);
246                break;
247            }
248        }
249        XmlUtils.nextElement(parser);
250        if ("usb-accessory".equals(parser.getName())) {
251            AccessoryFilter filter = AccessoryFilter.read(parser);
252            mAccessoryPreferenceMap.put(filter, packageName);
253        }
254        XmlUtils.nextElement(parser);
255    }
256
257    private void readSettingsLocked() {
258        FileInputStream stream = null;
259        try {
260            stream = new FileInputStream(sSettingsFile);
261            XmlPullParser parser = Xml.newPullParser();
262            parser.setInput(stream, null);
263
264            XmlUtils.nextElement(parser);
265            while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
266                String tagName = parser.getName();
267                if ("accessory-permission".equals(tagName)) {
268                    readAccessoryPermission(parser);
269                } else if ("preference".equals(tagName)) {
270                    readPreference(parser);
271                 } else {
272                    XmlUtils.nextElement(parser);
273                }
274            }
275        } catch (FileNotFoundException e) {
276            Log.w(TAG, "settings file not found");
277        } catch (Exception e) {
278            Log.e(TAG, "error reading settings file, deleting to start fresh", e);
279            sSettingsFile.delete();
280        } finally {
281            if (stream != null) {
282                try {
283                    stream.close();
284                } catch (IOException e) {
285                }
286            }
287        }
288    }
289
290    private void writeSettingsLocked() {
291        FileOutputStream fos = null;
292        try {
293            FileOutputStream fstr = new FileOutputStream(sSettingsFile);
294            Log.d(TAG, "writing settings to " + fstr);
295            BufferedOutputStream str = new BufferedOutputStream(fstr);
296            FastXmlSerializer serializer = new FastXmlSerializer();
297            serializer.setOutput(str, "utf-8");
298            serializer.startDocument(null, true);
299            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
300            serializer.startTag(null, "settings");
301
302            int count = mAccessoryPermissionMap.size();
303            for (int i = 0; i < count; i++) {
304                int uid = mAccessoryPermissionMap.keyAt(i);
305                ArrayList<AccessoryFilter> filters = mAccessoryPermissionMap.valueAt(i);
306                serializer.startTag(null, "accessory-permission");
307                serializer.attribute(null, "uid", Integer.toString(uid));
308                int filterCount = filters.size();
309                for (int j = 0; j < filterCount; j++) {
310                    filters.get(j).write(serializer);
311                }
312                serializer.endTag(null, "accessory-permission");
313            }
314
315            for (AccessoryFilter filter : mAccessoryPreferenceMap.keySet()) {
316                serializer.startTag(null, "preference");
317                serializer.attribute(null, "package", mAccessoryPreferenceMap.get(filter));
318                filter.write(serializer);
319                serializer.endTag(null, "preference");
320            }
321
322            serializer.endTag(null, "settings");
323            serializer.endDocument();
324
325            str.flush();
326            FileUtils.sync(fstr);
327            str.close();
328        } catch (Exception e) {
329            Log.e(TAG, "error writing settings file, deleting to start fresh", e);
330            sSettingsFile.delete();
331        }
332    }
333
334    // Checks to see if a package matches an accessory.
335    private boolean packageMatchesLocked(ResolveInfo info, String metaDataName,
336            UsbAccessory accessory) {
337        ActivityInfo ai = info.activityInfo;
338        PackageManager pm = mContext.getPackageManager();
339
340        XmlResourceParser parser = null;
341        try {
342            parser = ai.loadXmlMetaData(pm, metaDataName);
343            if (parser == null) {
344                Log.w(TAG, "no meta-data for " + info);
345                return false;
346            }
347
348            XmlUtils.nextElement(parser);
349            while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
350                String tagName = parser.getName();
351                if (accessory != null && "usb-accessory".equals(tagName)) {
352                    AccessoryFilter filter = AccessoryFilter.read(parser);
353                    if (filter.matches(accessory)) {
354                        return true;
355                    }
356                }
357                XmlUtils.nextElement(parser);
358            }
359        } catch (Exception e) {
360            Log.w(TAG, "Unable to load component info " + info.toString(), e);
361        } finally {
362            if (parser != null) parser.close();
363        }
364        return false;
365    }
366
367    private final ArrayList<ResolveInfo> getAccessoryMatchesLocked(
368            UsbAccessory accessory, Intent intent) {
369        ArrayList<ResolveInfo> matches = new ArrayList<ResolveInfo>();
370        PackageManager pm = mContext.getPackageManager();
371        List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent,
372                PackageManager.GET_META_DATA);
373        int count = resolveInfos.size();
374        for (int i = 0; i < count; i++) {
375            ResolveInfo resolveInfo = resolveInfos.get(i);
376            if (packageMatchesLocked(resolveInfo, intent.getAction(), accessory)) {
377                matches.add(resolveInfo);
378            }
379        }
380        return matches;
381    }
382
383    public void accessoryAttached(UsbAccessory accessory) {
384        Intent accessoryIntent = new Intent(UsbManager.ACTION_USB_ACCESSORY_ATTACHED);
385        accessoryIntent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
386        accessoryIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
387
388        ArrayList<ResolveInfo> matches;
389        String defaultPackage;
390        synchronized (mLock) {
391            matches = getAccessoryMatchesLocked(accessory, accessoryIntent);
392            // Launch our default activity directly, if we have one.
393            // Otherwise we will start the UsbResolverActivity to allow the user to choose.
394            defaultPackage = mAccessoryPreferenceMap.get(new AccessoryFilter(accessory));
395        }
396
397        int count = matches.size();
398        // don't show the resolver activity if there are no choices available
399        if (count == 0) return;
400
401        if (defaultPackage != null) {
402            for (int i = 0; i < count; i++) {
403                ResolveInfo rInfo = matches.get(i);
404                if (rInfo.activityInfo != null &&
405                        defaultPackage.equals(rInfo.activityInfo.packageName)) {
406                    try {
407                        accessoryIntent.setComponent(new ComponentName(
408                                defaultPackage, rInfo.activityInfo.name));
409                        mContext.startActivity(accessoryIntent);
410                    } catch (ActivityNotFoundException e) {
411                        Log.e(TAG, "startActivity failed", e);
412                    }
413                    return;
414                }
415            }
416        }
417
418        Intent intent = new Intent(mContext, UsbResolverActivity.class);
419        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
420
421        intent.putExtra(Intent.EXTRA_INTENT, accessoryIntent);
422        intent.putParcelableArrayListExtra(UsbResolverActivity.EXTRA_RESOLVE_INFOS, matches);
423        try {
424            mContext.startActivity(intent);
425        } catch (ActivityNotFoundException e) {
426            Log.w(TAG, "unable to start UsbResolverActivity");
427        }
428    }
429
430    public void accessoryDetached(UsbAccessory accessory) {
431        Intent intent = new Intent(
432                UsbManager.ACTION_USB_ACCESSORY_DETACHED);
433        intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
434        mContext.sendBroadcast(intent);
435    }
436
437    public void checkPermission(UsbAccessory accessory) {
438        if (accessory == null) return;
439        synchronized (mLock) {
440            ArrayList<AccessoryFilter> filterList = mAccessoryPermissionMap.get(Binder.getCallingUid());
441            if (filterList != null) {
442                int count = filterList.size();
443                for (int i = 0; i < count; i++) {
444                    AccessoryFilter filter = filterList.get(i);
445                    if (filter.equals(accessory)) {
446                        // permission allowed
447                        return;
448                    }
449                }
450            }
451        }
452        throw new SecurityException("User has not given permission to accessory " + accessory);
453    }
454
455    public void setAccessoryPackage(UsbAccessory accessory, String packageName) {
456        AccessoryFilter filter = new AccessoryFilter(accessory);
457        boolean changed = false;
458        synchronized (mLock) {
459            if (packageName == null) {
460                changed = (mAccessoryPreferenceMap.remove(filter) != null);
461            } else {
462                changed = !packageName.equals(mAccessoryPreferenceMap.get(filter));
463                if (changed) {
464                    mAccessoryPreferenceMap.put(filter, packageName);
465                }
466            }
467            if (changed) {
468                writeSettingsLocked();
469            }
470        }
471    }
472
473    public void grantAccessoryPermission(UsbAccessory accessory, int uid) {
474        synchronized (mLock) {
475            ArrayList<AccessoryFilter> filterList = mAccessoryPermissionMap.get(uid);
476            if (filterList == null) {
477                filterList = new ArrayList<AccessoryFilter>();
478                mAccessoryPermissionMap.put(uid, filterList);
479            } else {
480                int count = filterList.size();
481                for (int i = 0; i < count; i++) {
482                    if (filterList.get(i).equals(accessory)) return;
483                }
484            }
485            filterList.add(new AccessoryFilter(accessory));
486            writeSettingsLocked();
487        }
488    }
489
490    public boolean hasDefaults(String packageName, int uid) {
491        synchronized (mLock) {
492            if (mAccessoryPermissionMap.get(uid) != null) return true;
493            if (mAccessoryPreferenceMap.values().contains(packageName)) return true;
494            return false;
495        }
496    }
497
498    public void clearDefaults(String packageName, int uid) {
499        synchronized (mLock) {
500            boolean packageCleared = clearPackageDefaultsLocked(packageName);
501            boolean uidCleared = clearUidDefaultsLocked(uid);
502            if (packageCleared || uidCleared) {
503                writeSettingsLocked();
504            }
505        }
506    }
507
508    private boolean clearUidDefaultsLocked(int uid) {
509        boolean cleared = false;
510        int index = mAccessoryPermissionMap.indexOfKey(uid);
511        if (index >= 0) {
512            mAccessoryPermissionMap.removeAt(index);
513            cleared = true;
514        }
515        return cleared;
516    }
517
518    private boolean clearPackageDefaultsLocked(String packageName) {
519        boolean cleared = false;
520        synchronized (mLock) {
521            if (mAccessoryPreferenceMap.containsValue(packageName)) {
522                // make a copy of the key set to avoid ConcurrentModificationException
523                Object[] keys = mAccessoryPreferenceMap.keySet().toArray();
524                for (int i = 0; i < keys.length; i++) {
525                    Object key = keys[i];
526                    if (packageName.equals(mAccessoryPreferenceMap.get(key))) {
527                        mAccessoryPreferenceMap.remove(key);
528                        cleared = true;
529                    }
530                }
531            }
532            return cleared;
533        }
534    }
535
536    public void dump(FileDescriptor fd, PrintWriter pw) {
537        synchronized (mLock) {
538            pw.println("  Accessory permissions:");
539            int count = mAccessoryPermissionMap.size();
540            for (int i = 0; i < count; i++) {
541                int uid = mAccessoryPermissionMap.keyAt(i);
542                pw.println("    " + "uid " + uid + ":");
543                ArrayList<AccessoryFilter> filters = mAccessoryPermissionMap.valueAt(i);
544                for (AccessoryFilter filter : filters) {
545                    pw.println("      " + filter);
546                }
547            }
548            pw.println("  Accessory preferences:");
549            for (AccessoryFilter filter : mAccessoryPreferenceMap.keySet()) {
550                pw.println("    " + filter + ": " + mAccessoryPreferenceMap.get(filter));
551            }
552        }
553    }
554}
555