UsbDeviceSettingsManager.java revision 2cc0377200b94b2f68f34e34554f2aa39e09cbce
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.UsbManager; 33import android.os.Binder; 34import android.os.FileUtils; 35import android.os.Process; 36import android.util.Log; 37import android.util.SparseBooleanArray; 38import android.util.Xml; 39 40import com.android.internal.content.PackageMonitor; 41import com.android.internal.util.FastXmlSerializer; 42import com.android.internal.util.XmlUtils; 43 44import org.xmlpull.v1.XmlPullParser; 45import org.xmlpull.v1.XmlPullParserException; 46import org.xmlpull.v1.XmlSerializer; 47 48import java.io.BufferedOutputStream; 49import java.io.File; 50import java.io.FileDescriptor; 51import java.io.FileInputStream; 52import java.io.FileNotFoundException; 53import java.io.FileOutputStream; 54import java.io.IOException; 55import java.io.PrintWriter; 56import java.util.ArrayList; 57import java.util.HashMap; 58import java.util.List; 59 60class UsbDeviceSettingsManager { 61 62 private static final String TAG = "UsbDeviceSettingsManager"; 63 private static final File sSettingsFile = new File("/data/system/usb_device_manager.xml"); 64 65 private final Context mContext; 66 private final PackageManager mPackageManager; 67 68 // Temporary mapping UsbAccessory to list of UIDs with permissions for the accessory 69 private final HashMap<UsbAccessory, SparseBooleanArray> mAccessoryPermissionMap = 70 new HashMap<UsbAccessory, SparseBooleanArray>(); 71 // Maps AccessoryFilter to user preferred application package 72 private final HashMap<AccessoryFilter, String> mAccessoryPreferenceMap = 73 new HashMap<AccessoryFilter, String>(); 74 75 private final Object mLock = new Object(); 76 77 // This class is used to describe a USB accessory. 78 // When used in HashMaps all values must be specified, 79 // but wildcards can be used for any of the fields in 80 // the package meta-data. 81 private static class AccessoryFilter { 82 // USB accessory manufacturer (or null for unspecified) 83 public final String mManufacturer; 84 // USB accessory model (or null for unspecified) 85 public final String mModel; 86 // USB accessory version (or null for unspecified) 87 public final String mVersion; 88 89 public AccessoryFilter(String manufacturer, String model, String version) { 90 mManufacturer = manufacturer; 91 mModel = model; 92 mVersion = version; 93 } 94 95 public AccessoryFilter(UsbAccessory accessory) { 96 mManufacturer = accessory.getManufacturer(); 97 mModel = accessory.getModel(); 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 version = null; 106 107 int count = parser.getAttributeCount(); 108 for (int i = 0; i < count; i++) { 109 String name = parser.getAttributeName(i); 110 String value = parser.getAttributeValue(i); 111 112 if ("manufacturer".equals(name)) { 113 manufacturer = value; 114 } else if ("model".equals(name)) { 115 model = value; 116 } else if ("version".equals(name)) { 117 version = value; 118 } 119 } 120 return new AccessoryFilter(manufacturer, model, version); 121 } 122 123 public void write(XmlSerializer serializer)throws IOException { 124 serializer.startTag(null, "usb-accessory"); 125 if (mManufacturer != null) { 126 serializer.attribute(null, "manufacturer", mManufacturer); 127 } 128 if (mModel != null) { 129 serializer.attribute(null, "model", mModel); 130 } 131 if (mVersion != null) { 132 serializer.attribute(null, "version", mVersion); 133 } 134 serializer.endTag(null, "usb-accessory"); 135 } 136 137 public boolean matches(UsbAccessory acc) { 138 if (mManufacturer != null && !acc.getManufacturer().equals(mManufacturer)) return false; 139 if (mModel != null && !acc.getModel().equals(mModel)) return false; 140 if (mVersion != null && !acc.getVersion().equals(mVersion)) return false; 141 return true; 142 } 143 144 public boolean matches(AccessoryFilter f) { 145 if (mManufacturer != null && !f.mManufacturer.equals(mManufacturer)) return false; 146 if (mModel != null && !f.mModel.equals(mModel)) return false; 147 if (mVersion != null && !f.mVersion.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 || 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 mVersion.equals(filter.mVersion)); 162 } 163 if (obj instanceof UsbAccessory) { 164 UsbAccessory accessory = (UsbAccessory)obj; 165 return (mManufacturer.equals(accessory.getManufacturer()) && 166 mModel.equals(accessory.getModel()) && 167 mVersion.equals(accessory.getVersion())); 168 } 169 return false; 170 } 171 172 @Override 173 public int hashCode() { 174 return ((mManufacturer == null ? 0 : mManufacturer.hashCode()) ^ 175 (mModel == null ? 0 : mModel.hashCode()) ^ 176 (mVersion == null ? 0 : mVersion.hashCode())); 177 } 178 179 @Override 180 public String toString() { 181 return "AccessoryFilter[mManufacturer=\"" + mManufacturer + 182 "\", mModel=\"" + mModel + 183 "\", mVersion=\"" + mVersion + "\"]"; 184 } 185 } 186 187 private class MyPackageMonitor extends PackageMonitor { 188 189 public void onPackageAdded(String packageName, int uid) { 190 handlePackageUpdate(packageName); 191 } 192 193 public void onPackageChanged(String packageName, int uid, String[] components) { 194 handlePackageUpdate(packageName); 195 } 196 197 public void onPackageRemoved(String packageName, int uid) { 198 clearDefaults(packageName); 199 } 200 } 201 MyPackageMonitor mPackageMonitor = new MyPackageMonitor(); 202 203 public UsbDeviceSettingsManager(Context context) { 204 mContext = context; 205 mPackageManager = context.getPackageManager(); 206 synchronized (mLock) { 207 readSettingsLocked(); 208 } 209 mPackageMonitor.register(context, true); 210 } 211 212 private void readPreference(XmlPullParser parser) 213 throws XmlPullParserException, IOException { 214 String packageName = null; 215 int count = parser.getAttributeCount(); 216 for (int i = 0; i < count; i++) { 217 if ("package".equals(parser.getAttributeName(i))) { 218 packageName = parser.getAttributeValue(i); 219 break; 220 } 221 } 222 XmlUtils.nextElement(parser); 223 if ("usb-accessory".equals(parser.getName())) { 224 AccessoryFilter filter = AccessoryFilter.read(parser); 225 mAccessoryPreferenceMap.put(filter, packageName); 226 } 227 XmlUtils.nextElement(parser); 228 } 229 230 private void readSettingsLocked() { 231 FileInputStream stream = null; 232 try { 233 stream = new FileInputStream(sSettingsFile); 234 XmlPullParser parser = Xml.newPullParser(); 235 parser.setInput(stream, null); 236 237 XmlUtils.nextElement(parser); 238 while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { 239 String tagName = parser.getName(); 240 if ("preference".equals(tagName)) { 241 readPreference(parser); 242 } else { 243 XmlUtils.nextElement(parser); 244 } 245 } 246 } catch (FileNotFoundException e) { 247 Log.w(TAG, "settings file not found"); 248 } catch (Exception e) { 249 Log.e(TAG, "error reading settings file, deleting to start fresh", e); 250 sSettingsFile.delete(); 251 } finally { 252 if (stream != null) { 253 try { 254 stream.close(); 255 } catch (IOException e) { 256 } 257 } 258 } 259 } 260 261 private void writeSettingsLocked() { 262 FileOutputStream fos = null; 263 try { 264 FileOutputStream fstr = new FileOutputStream(sSettingsFile); 265 Log.d(TAG, "writing settings to " + fstr); 266 BufferedOutputStream str = new BufferedOutputStream(fstr); 267 FastXmlSerializer serializer = new FastXmlSerializer(); 268 serializer.setOutput(str, "utf-8"); 269 serializer.startDocument(null, true); 270 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 271 serializer.startTag(null, "settings"); 272 273 for (AccessoryFilter filter : mAccessoryPreferenceMap.keySet()) { 274 serializer.startTag(null, "preference"); 275 serializer.attribute(null, "package", mAccessoryPreferenceMap.get(filter)); 276 filter.write(serializer); 277 serializer.endTag(null, "preference"); 278 } 279 280 serializer.endTag(null, "settings"); 281 serializer.endDocument(); 282 283 str.flush(); 284 FileUtils.sync(fstr); 285 str.close(); 286 } catch (Exception e) { 287 Log.e(TAG, "error writing settings file, deleting to start fresh", e); 288 sSettingsFile.delete(); 289 } 290 } 291 292 // Checks to see if a package matches an accessory. 293 private boolean packageMatchesLocked(ResolveInfo info, String metaDataName, 294 UsbAccessory accessory) { 295 ActivityInfo ai = info.activityInfo; 296 297 XmlResourceParser parser = null; 298 try { 299 parser = ai.loadXmlMetaData(mPackageManager, metaDataName); 300 if (parser == null) { 301 Log.w(TAG, "no meta-data for " + info); 302 return false; 303 } 304 305 XmlUtils.nextElement(parser); 306 while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { 307 String tagName = parser.getName(); 308 if (accessory != null && "usb-accessory".equals(tagName)) { 309 AccessoryFilter filter = AccessoryFilter.read(parser); 310 if (filter.matches(accessory)) { 311 return true; 312 } 313 } 314 XmlUtils.nextElement(parser); 315 } 316 } catch (Exception e) { 317 Log.w(TAG, "Unable to load component info " + info.toString(), e); 318 } finally { 319 if (parser != null) parser.close(); 320 } 321 return false; 322 } 323 324 private final ArrayList<ResolveInfo> getAccessoryMatchesLocked( 325 UsbAccessory accessory, Intent intent) { 326 ArrayList<ResolveInfo> matches = new ArrayList<ResolveInfo>(); 327 List<ResolveInfo> resolveInfos = mPackageManager.queryIntentActivities(intent, 328 PackageManager.GET_META_DATA); 329 int count = resolveInfos.size(); 330 for (int i = 0; i < count; i++) { 331 ResolveInfo resolveInfo = resolveInfos.get(i); 332 if (packageMatchesLocked(resolveInfo, intent.getAction(), accessory)) { 333 matches.add(resolveInfo); 334 } 335 } 336 return matches; 337 } 338 339 public void accessoryAttached(UsbAccessory accessory) { 340 Intent intent = new Intent(UsbManager.ACTION_USB_ACCESSORY_ATTACHED); 341 intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); 342 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 343 344 ArrayList<ResolveInfo> matches; 345 String defaultPackage; 346 synchronized (mLock) { 347 matches = getAccessoryMatchesLocked(accessory, intent); 348 // Launch our default activity directly, if we have one. 349 // Otherwise we will start the UsbResolverActivity to allow the user to choose. 350 defaultPackage = mAccessoryPreferenceMap.get(new AccessoryFilter(accessory)); 351 } 352 353 resolveActivity(intent, matches, defaultPackage, accessory); 354 } 355 356 public void accessoryDetached(UsbAccessory accessory) { 357 // clear temporary permissions for the accessory 358 mAccessoryPermissionMap.remove(accessory); 359 360 Intent intent = new Intent( 361 UsbManager.ACTION_USB_ACCESSORY_DETACHED); 362 intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); 363 mContext.sendBroadcast(intent); 364 } 365 366 private void resolveActivity(Intent intent, ArrayList<ResolveInfo> matches, 367 String defaultPackage, UsbAccessory accessory) { 368 int count = matches.size(); 369 370 // don't show the resolver activity if there are no choices available 371 if (count == 0) { 372 if (accessory != null) { 373 String uri = accessory.getUri(); 374 if (uri != null && uri.length() > 0) { 375 // display URI to user 376 // start UsbResolverActivity so user can choose an activity 377 Intent dialogIntent = new Intent(); 378 dialogIntent.setClassName("com.android.systemui", 379 "com.android.systemui.usb.UsbAccessoryUriActivity"); 380 dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 381 dialogIntent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); 382 dialogIntent.putExtra("uri", uri); 383 try { 384 mContext.startActivity(dialogIntent); 385 } catch (ActivityNotFoundException e) { 386 Log.e(TAG, "unable to start UsbAccessoryUriActivity"); 387 } 388 } 389 } 390 391 // do nothing 392 return; 393 } 394 395 ResolveInfo defaultRI = null; 396 if (count == 1 && defaultPackage == null) { 397 // Check to see if our single choice is on the system partition. 398 // If so, treat it as our default without calling UsbResolverActivity 399 ResolveInfo rInfo = matches.get(0); 400 if (rInfo.activityInfo != null && 401 rInfo.activityInfo.applicationInfo != null && 402 (rInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 403 defaultRI = rInfo; 404 } 405 } 406 407 if (defaultRI == null && defaultPackage != null) { 408 // look for default activity 409 for (int i = 0; i < count; i++) { 410 ResolveInfo rInfo = matches.get(i); 411 if (rInfo.activityInfo != null && 412 defaultPackage.equals(rInfo.activityInfo.packageName)) { 413 defaultRI = rInfo; 414 break; 415 } 416 } 417 } 418 419 if (defaultRI != null) { 420 // grant permission for default activity 421 grantAccessoryPermission(accessory, defaultRI.activityInfo.applicationInfo.uid); 422 423 // start default activity directly 424 try { 425 intent.setComponent( 426 new ComponentName(defaultRI.activityInfo.packageName, 427 defaultRI.activityInfo.name)); 428 mContext.startActivity(intent); 429 } catch (ActivityNotFoundException e) { 430 Log.e(TAG, "startActivity failed", e); 431 } 432 } else { 433 // start UsbResolverActivity so user can choose an activity 434 Intent resolverIntent = new Intent(); 435 resolverIntent.setClassName("com.android.systemui", 436 "com.android.systemui.usb.UsbResolverActivity"); 437 resolverIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 438 resolverIntent.putExtra(Intent.EXTRA_INTENT, intent); 439 resolverIntent.putParcelableArrayListExtra("rlist", matches); 440 try { 441 mContext.startActivity(resolverIntent); 442 } catch (ActivityNotFoundException e) { 443 Log.e(TAG, "unable to start UsbResolverActivity"); 444 } 445 } 446 } 447 448 private boolean clearCompatibleMatchesLocked(String packageName, AccessoryFilter filter) { 449 boolean changed = false; 450 for (AccessoryFilter test : mAccessoryPreferenceMap.keySet()) { 451 if (filter.matches(test)) { 452 mAccessoryPreferenceMap.remove(test); 453 changed = true; 454 } 455 } 456 return changed; 457 } 458 459 private boolean handlePackageUpdateLocked(String packageName, ActivityInfo aInfo, 460 String metaDataName) { 461 XmlResourceParser parser = null; 462 boolean changed = false; 463 464 try { 465 parser = aInfo.loadXmlMetaData(mPackageManager, metaDataName); 466 if (parser == null) return false; 467 468 XmlUtils.nextElement(parser); 469 while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { 470 String tagName = parser.getName(); 471 if ("usb-accessory".equals(tagName)) { 472 AccessoryFilter filter = AccessoryFilter.read(parser); 473 if (clearCompatibleMatchesLocked(packageName, filter)) { 474 changed = true; 475 } 476 } 477 XmlUtils.nextElement(parser); 478 } 479 } catch (Exception e) { 480 Log.w(TAG, "Unable to load component info " + aInfo.toString(), e); 481 } finally { 482 if (parser != null) parser.close(); 483 } 484 return changed; 485 } 486 487 // Check to see if the package supports any USB devices or accessories. 488 // If so, clear any non-matching preferences for matching devices/accessories. 489 private void handlePackageUpdate(String packageName) { 490 synchronized (mLock) { 491 PackageInfo info; 492 boolean changed = false; 493 494 try { 495 info = mPackageManager.getPackageInfo(packageName, 496 PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA); 497 } catch (NameNotFoundException e) { 498 Log.e(TAG, "handlePackageUpdate could not find package " + packageName, e); 499 return; 500 } 501 502 ActivityInfo[] activities = info.activities; 503 if (activities == null) return; 504 for (int i = 0; i < activities.length; i++) { 505 // check for meta-data, both for devices and accessories 506 if (handlePackageUpdateLocked(packageName, activities[i], 507 UsbManager.ACTION_USB_ACCESSORY_ATTACHED)) { 508 changed = true; 509 } 510 } 511 512 if (changed) { 513 writeSettingsLocked(); 514 } 515 } 516 } 517 518 public boolean hasPermission(UsbAccessory accessory) { 519 synchronized (mLock) { 520 SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory); 521 if (uidList == null) { 522 return false; 523 } 524 return uidList.get(Binder.getCallingUid()); 525 } 526 } 527 528 public void checkPermission(UsbAccessory accessory) { 529 if (!hasPermission(accessory)) { 530 throw new SecurityException("User has not given permission to accessory " + accessory); 531 } 532 } 533 534 private void requestPermissionDialog(Intent intent, String packageName, PendingIntent pi) { 535 int uid = Binder.getCallingUid(); 536 537 // compare uid with packageName to foil apps pretending to be someone else 538 try { 539 ApplicationInfo aInfo = mPackageManager.getApplicationInfo(packageName, 0); 540 if (aInfo.uid != uid) { 541 throw new IllegalArgumentException("package " + packageName + 542 " does not match caller's uid " + uid); 543 } 544 } catch (PackageManager.NameNotFoundException e) { 545 throw new IllegalArgumentException("package " + packageName + " not found"); 546 } 547 548 long identity = Binder.clearCallingIdentity(); 549 intent.setClassName("com.android.systemui", 550 "com.android.systemui.usb.UsbPermissionActivity"); 551 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 552 intent.putExtra(Intent.EXTRA_INTENT, pi); 553 intent.putExtra("package", packageName); 554 intent.putExtra("uid", uid); 555 try { 556 mContext.startActivity(intent); 557 } catch (ActivityNotFoundException e) { 558 Log.e(TAG, "unable to start UsbPermissionActivity"); 559 } finally { 560 Binder.restoreCallingIdentity(identity); 561 } 562 } 563 564 public void requestPermission(UsbAccessory accessory, String packageName, PendingIntent pi) { 565 Intent intent = new Intent(); 566 567 // respond immediately if permission has already been granted 568 if (hasPermission(accessory)) { 569 intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); 570 intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true); 571 try { 572 pi.send(mContext, 0, intent); 573 } catch (PendingIntent.CanceledException e) { 574 Log.w(TAG, "requestPermission PendingIntent was cancelled"); 575 } 576 return; 577 } 578 579 intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); 580 requestPermissionDialog(intent, packageName, pi); 581 } 582 583 public void setAccessoryPackage(UsbAccessory accessory, String packageName) { 584 AccessoryFilter filter = new AccessoryFilter(accessory); 585 boolean changed = false; 586 synchronized (mLock) { 587 if (packageName == null) { 588 changed = (mAccessoryPreferenceMap.remove(filter) != null); 589 } else { 590 changed = !packageName.equals(mAccessoryPreferenceMap.get(filter)); 591 if (changed) { 592 mAccessoryPreferenceMap.put(filter, packageName); 593 } 594 } 595 if (changed) { 596 writeSettingsLocked(); 597 } 598 } 599 } 600 601 public void grantAccessoryPermission(UsbAccessory accessory, int uid) { 602 synchronized (mLock) { 603 SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory); 604 if (uidList == null) { 605 uidList = new SparseBooleanArray(1); 606 mAccessoryPermissionMap.put(accessory, uidList); 607 } 608 uidList.put(uid, true); 609 } 610 } 611 612 public boolean hasDefaults(String packageName) { 613 synchronized (mLock) { 614 return mAccessoryPreferenceMap.values().contains(packageName); 615 } 616 } 617 618 public void clearDefaults(String packageName) { 619 synchronized (mLock) { 620 if (clearPackageDefaultsLocked(packageName)) { 621 writeSettingsLocked(); 622 } 623 } 624 } 625 626 private boolean clearPackageDefaultsLocked(String packageName) { 627 boolean cleared = false; 628 synchronized (mLock) { 629 if (mAccessoryPreferenceMap.containsValue(packageName)) { 630 // make a copy of the key set to avoid ConcurrentModificationException 631 Object[] keys = mAccessoryPreferenceMap.keySet().toArray(); 632 for (int i = 0; i < keys.length; i++) { 633 Object key = keys[i]; 634 if (packageName.equals(mAccessoryPreferenceMap.get(key))) { 635 mAccessoryPreferenceMap.remove(key); 636 cleared = true; 637 } 638 } 639 } 640 return cleared; 641 } 642 } 643 644 public void dump(FileDescriptor fd, PrintWriter pw) { 645 synchronized (mLock) { 646 pw.println(" Accessory permissions:"); 647 for (UsbAccessory accessory : mAccessoryPermissionMap.keySet()) { 648 pw.print(" " + accessory + ": "); 649 SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory); 650 int count = uidList.size(); 651 for (int i = 0; i < count; i++) { 652 pw.print(Integer.toString(uidList.keyAt(i)) + " "); 653 } 654 pw.println(""); 655 } 656 pw.println(" Accessory preferences:"); 657 for (AccessoryFilter filter : mAccessoryPreferenceMap.keySet()) { 658 pw.println(" " + filter + ": " + mAccessoryPreferenceMap.get(filter)); 659 } 660 } 661 } 662} 663