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