1 /* 2 * Copyright (C) 2008 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 17 package com.android.sdklib; 18 19 import com.android.annotations.VisibleForTesting; 20 import com.android.annotations.VisibleForTesting.Visibility; 21 import com.android.io.FileWrapper; 22 import com.android.prefs.AndroidLocation; 23 import com.android.prefs.AndroidLocation.AndroidLocationException; 24 import com.android.sdklib.AndroidVersion.AndroidVersionException; 25 import com.android.sdklib.ISystemImage.LocationType; 26 import com.android.sdklib.internal.project.ProjectProperties; 27 import com.android.sdklib.repository.PkgProps; 28 import com.android.util.Pair; 29 30 import java.io.File; 31 import java.io.FileInputStream; 32 import java.io.FileNotFoundException; 33 import java.io.FileWriter; 34 import java.io.IOException; 35 import java.util.ArrayList; 36 import java.util.Collections; 37 import java.util.HashMap; 38 import java.util.HashSet; 39 import java.util.Map; 40 import java.util.Properties; 41 import java.util.Set; 42 import java.util.TreeSet; 43 import java.util.regex.Matcher; 44 import java.util.regex.Pattern; 45 46 /** 47 * The SDK manager parses the SDK folder and gives access to the content. 48 * @see PlatformTarget 49 * @see AddOnTarget 50 */ 51 public class SdkManager { 52 53 public final static String PROP_VERSION_SDK = "ro.build.version.sdk"; //$NON-NLS-1$ 54 public final static String PROP_VERSION_CODENAME = "ro.build.version.codename"; //$NON-NLS-1$ 55 public final static String PROP_VERSION_RELEASE = "ro.build.version.release"; //$NON-NLS-1$ 56 57 public final static String ADDON_NAME = "name"; //$NON-NLS-1$ 58 public final static String ADDON_VENDOR = "vendor"; //$NON-NLS-1$ 59 public final static String ADDON_API = "api"; //$NON-NLS-1$ 60 public final static String ADDON_DESCRIPTION = "description"; //$NON-NLS-1$ 61 public final static String ADDON_LIBRARIES = "libraries"; //$NON-NLS-1$ 62 public final static String ADDON_DEFAULT_SKIN = "skin"; //$NON-NLS-1$ 63 public final static String ADDON_USB_VENDOR = "usb-vendor"; //$NON-NLS-1$ 64 public final static String ADDON_REVISION = "revision"; //$NON-NLS-1$ 65 public final static String ADDON_REVISION_OLD = "version"; //$NON-NLS-1$ 66 67 68 private final static Pattern PATTERN_LIB_DATA = Pattern.compile( 69 "^([a-zA-Z0-9._-]+\\.jar);(.*)$", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$ 70 71 // usb ids are 16-bit hexadecimal values. 72 private final static Pattern PATTERN_USB_IDS = Pattern.compile( 73 "^0x[a-f0-9]{4}$", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$ 74 75 /** List of items in the platform to check when parsing it. These paths are relative to the 76 * platform root folder. */ 77 private final static String[] sPlatformContentList = new String[] { 78 SdkConstants.FN_FRAMEWORK_LIBRARY, 79 SdkConstants.FN_FRAMEWORK_AIDL, 80 }; 81 82 /** Preference file containing the usb ids for adb */ 83 private final static String ADB_INI_FILE = "adb_usb.ini"; //$NON-NLS-1$ 84 //0--------90--------90--------90--------90--------90--------90--------90--------9 85 private final static String ADB_INI_HEADER = 86 "# ANDROID 3RD PARTY USB VENDOR ID LIST -- DO NOT EDIT.\n" + //$NON-NLS-1$ 87 "# USE 'android update adb' TO GENERATE.\n" + //$NON-NLS-1$ 88 "# 1 USB VENDOR ID PER LINE.\n"; //$NON-NLS-1$ 89 90 /** The location of the SDK as an OS path */ 91 private final String mOsSdkPath; 92 /** Valid targets that have been loaded. Can be empty but not null. */ 93 private IAndroidTarget[] mTargets = new IAndroidTarget[0]; 94 95 public static class LayoutlibVersion implements Comparable<LayoutlibVersion> { 96 private final int mApi; 97 private final int mRevision; 98 99 public static final int NOT_SPECIFIED = 0; 100 101 public LayoutlibVersion(int api, int revision) { 102 mApi = api; 103 mRevision = revision; 104 } 105 106 public int getApi() { 107 return mApi; 108 } 109 110 public int getRevision() { 111 return mRevision; 112 } 113 114 public int compareTo(LayoutlibVersion rhs) { 115 boolean useRev = this.mRevision > NOT_SPECIFIED && rhs.mRevision > NOT_SPECIFIED; 116 int lhsValue = (this.mApi << 16) + (useRev ? this.mRevision : 0); 117 int rhsValue = (rhs.mApi << 16) + (useRev ? rhs.mRevision : 0); 118 return lhsValue - rhsValue; 119 } 120 } 121 122 /** 123 * Create a new {@link SdkManager} instance. 124 * External users should use {@link #createManager(String, ISdkLog)}. 125 * 126 * @param osSdkPath the location of the SDK. 127 */ 128 @VisibleForTesting(visibility=Visibility.PRIVATE) 129 protected SdkManager(String osSdkPath) { 130 mOsSdkPath = osSdkPath; 131 } 132 133 /** 134 * Creates an {@link SdkManager} for a given sdk location. 135 * @param osSdkPath the location of the SDK. 136 * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null. 137 * @return the created {@link SdkManager} or null if the location is not valid. 138 */ 139 public static SdkManager createManager(String osSdkPath, ISdkLog log) { 140 try { 141 SdkManager manager = new SdkManager(osSdkPath); 142 ArrayList<IAndroidTarget> list = new ArrayList<IAndroidTarget>(); 143 loadPlatforms(osSdkPath, list, log); 144 loadAddOns(osSdkPath, list, log); 145 146 // sort the targets/add-ons 147 Collections.sort(list); 148 149 manager.setTargets(list.toArray(new IAndroidTarget[list.size()])); 150 151 // load the samples, after the targets have been set. 152 manager.loadSamples(log); 153 154 return manager; 155 } catch (IllegalArgumentException e) { 156 log.error(e, "Error parsing the sdk."); 157 } 158 159 return null; 160 } 161 162 /** 163 * Returns the location of the SDK. 164 */ 165 public String getLocation() { 166 return mOsSdkPath; 167 } 168 169 /** 170 * Returns the targets that are available in the SDK. 171 * <p/> 172 * The array can be empty but not null. 173 */ 174 public IAndroidTarget[] getTargets() { 175 return mTargets; 176 } 177 178 /** 179 * Sets the targets that are available in the SDK. 180 * <p/> 181 * The array can be empty but not null. 182 */ 183 @VisibleForTesting(visibility=Visibility.PRIVATE) 184 protected void setTargets(IAndroidTarget[] targets) { 185 assert targets != null; 186 mTargets = targets; 187 } 188 189 /** 190 * Returns a target from a hash that was generated by {@link IAndroidTarget#hashString()}. 191 * 192 * @param hash the {@link IAndroidTarget} hash string. 193 * @return The matching {@link IAndroidTarget} or null. 194 */ 195 public IAndroidTarget getTargetFromHashString(String hash) { 196 if (hash != null) { 197 for (IAndroidTarget target : mTargets) { 198 if (hash.equals(target.hashString())) { 199 return target; 200 } 201 } 202 } 203 204 return null; 205 } 206 207 /** 208 * Updates adb with the USB devices declared in the SDK add-ons. 209 * @throws AndroidLocationException 210 * @throws IOException 211 */ 212 public void updateAdb() throws AndroidLocationException, IOException { 213 FileWriter writer = null; 214 try { 215 // get the android prefs location to know where to write the file. 216 File adbIni = new File(AndroidLocation.getFolder(), ADB_INI_FILE); 217 writer = new FileWriter(adbIni); 218 219 // first, put all the vendor id in an HashSet to remove duplicate. 220 HashSet<Integer> set = new HashSet<Integer>(); 221 IAndroidTarget[] targets = getTargets(); 222 for (IAndroidTarget target : targets) { 223 if (target.getUsbVendorId() != IAndroidTarget.NO_USB_ID) { 224 set.add(target.getUsbVendorId()); 225 } 226 } 227 228 // write file header. 229 writer.write(ADB_INI_HEADER); 230 231 // now write the Id in a text file, one per line. 232 for (Integer i : set) { 233 writer.write(String.format("0x%04x\n", i)); //$NON-NLS-1$ 234 } 235 } finally { 236 if (writer != null) { 237 writer.close(); 238 } 239 } 240 } 241 242 /** 243 * Reloads the content of the SDK. 244 * 245 * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null. 246 */ 247 public void reloadSdk(ISdkLog log) { 248 // get the current target list. 249 ArrayList<IAndroidTarget> list = new ArrayList<IAndroidTarget>(); 250 loadPlatforms(mOsSdkPath, list, log); 251 loadAddOns(mOsSdkPath, list, log); 252 253 // For now replace the old list with the new one. 254 // In the future we may want to keep the current objects, so that ADT doesn't have to deal 255 // with new IAndroidTarget objects when a target didn't actually change. 256 257 // sort the targets/add-ons 258 Collections.sort(list); 259 setTargets(list.toArray(new IAndroidTarget[list.size()])); 260 261 // load the samples, after the targets have been set. 262 loadSamples(log); 263 } 264 265 /** 266 * Returns the greatest {@link LayoutlibVersion} found amongst all platform 267 * targets currently loaded in the SDK. 268 * <p/> 269 * We only started recording Layoutlib Versions recently in the platform meta data 270 * so it's possible to have an SDK with many platforms loaded but no layoutlib 271 * version defined. 272 * 273 * @return The greatest {@link LayoutlibVersion} or null if none is found. 274 * @deprecated This does NOT solve the right problem and will be changed in Tools R15. 275 */ 276 @Deprecated 277 public LayoutlibVersion getMaxLayoutlibVersion() { 278 LayoutlibVersion maxVersion = null; 279 280 for (IAndroidTarget target : getTargets()) { 281 if (target instanceof PlatformTarget) { 282 LayoutlibVersion lv = ((PlatformTarget) target).getLayoutlibVersion(); 283 if (lv != null) { 284 if (maxVersion == null || lv.compareTo(maxVersion) > 0) { 285 maxVersion = lv; 286 } 287 } 288 } 289 } 290 291 return maxVersion; 292 } 293 294 /** 295 * Loads the Platforms from the SDK. 296 * Creates the "platforms" folder if necessary. 297 * 298 * @param sdkOsPath Location of the SDK 299 * @param list the list to fill with the platforms. 300 * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null. 301 * @throws RuntimeException when the "platforms" folder is missing and cannot be created. 302 */ 303 private static void loadPlatforms(String sdkOsPath, ArrayList<IAndroidTarget> list, 304 ISdkLog log) { 305 File platformFolder = new File(sdkOsPath, SdkConstants.FD_PLATFORMS); 306 307 if (platformFolder.isDirectory()) { 308 File[] platforms = platformFolder.listFiles(); 309 310 for (File platform : platforms) { 311 if (platform.isDirectory()) { 312 PlatformTarget target = loadPlatform(sdkOsPath, platform, log); 313 if (target != null) { 314 list.add(target); 315 } 316 } else { 317 log.warning("Ignoring platform '%1$s', not a folder.", platform.getName()); 318 } 319 } 320 321 return; 322 } 323 324 // Try to create it or complain if something else is in the way. 325 if (!platformFolder.exists()) { 326 if (!platformFolder.mkdir()) { 327 throw new RuntimeException( 328 String.format("Failed to create %1$s.", 329 platformFolder.getAbsolutePath())); 330 } 331 } else { 332 throw new RuntimeException( 333 String.format("%1$s is not a folder.", 334 platformFolder.getAbsolutePath())); 335 } 336 } 337 338 /** 339 * Loads a specific Platform at a given location. 340 * @param sdkOsPath Location of the SDK 341 * @param platformFolder the root folder of the platform. 342 * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null. 343 */ 344 private static PlatformTarget loadPlatform(String sdkOsPath, File platformFolder, 345 ISdkLog log) { 346 FileWrapper buildProp = new FileWrapper(platformFolder, SdkConstants.FN_BUILD_PROP); 347 FileWrapper sourcePropFile = new FileWrapper(platformFolder, SdkConstants.FN_SOURCE_PROP); 348 349 if (buildProp.isFile() && sourcePropFile.isFile()) { 350 Map<String, String> platformProp = new HashMap<String, String>(); 351 352 // add all the property files 353 Map<String, String> map = ProjectProperties.parsePropertyFile(buildProp, log); 354 if (map != null) { 355 platformProp.putAll(map); 356 } 357 358 map = ProjectProperties.parsePropertyFile(sourcePropFile, log); 359 if (map != null) { 360 platformProp.putAll(map); 361 } 362 363 FileWrapper sdkPropFile = new FileWrapper(platformFolder, SdkConstants.FN_SDK_PROP); 364 if (sdkPropFile.isFile()) { // obsolete platforms don't have this. 365 map = ProjectProperties.parsePropertyFile(sdkPropFile, log); 366 if (map != null) { 367 platformProp.putAll(map); 368 } 369 } 370 371 // look for some specific values in the map. 372 373 // api level 374 int apiNumber; 375 String stringValue = platformProp.get(PROP_VERSION_SDK); 376 if (stringValue == null) { 377 log.warning( 378 "Ignoring platform '%1$s': %2$s is missing from '%3$s'", 379 platformFolder.getName(), PROP_VERSION_SDK, 380 SdkConstants.FN_BUILD_PROP); 381 return null; 382 } else { 383 try { 384 apiNumber = Integer.parseInt(stringValue); 385 } catch (NumberFormatException e) { 386 // looks like apiNumber does not parse to a number. 387 // Ignore this platform. 388 log.warning( 389 "Ignoring platform '%1$s': %2$s is not a valid number in %3$s.", 390 platformFolder.getName(), PROP_VERSION_SDK, 391 SdkConstants.FN_BUILD_PROP); 392 return null; 393 } 394 } 395 396 // codename (optional) 397 String apiCodename = platformProp.get(PROP_VERSION_CODENAME); 398 if (apiCodename != null && apiCodename.equals("REL")) { 399 apiCodename = null; // REL means it's a release version and therefore the 400 // codename is irrelevant at this point. 401 } 402 403 // version string 404 String apiName = platformProp.get(PkgProps.PLATFORM_VERSION); 405 if (apiName == null) { 406 apiName = platformProp.get(PROP_VERSION_RELEASE); 407 } 408 if (apiName == null) { 409 log.warning( 410 "Ignoring platform '%1$s': %2$s is missing from '%3$s'", 411 platformFolder.getName(), PROP_VERSION_RELEASE, 412 SdkConstants.FN_BUILD_PROP); 413 return null; 414 } 415 416 // platform rev number & layoutlib version are extracted from the source.properties 417 // saved by the SDK Manager when installing the package. 418 419 int revision = 1; 420 LayoutlibVersion layoutlibVersion = null; 421 try { 422 revision = Integer.parseInt(platformProp.get(PkgProps.PKG_REVISION)); 423 } catch (NumberFormatException e) { 424 // do nothing, we'll keep the default value of 1. 425 } 426 427 try { 428 String propApi = platformProp.get(PkgProps.LAYOUTLIB_API); 429 String propRev = platformProp.get(PkgProps.LAYOUTLIB_REV); 430 int llApi = propApi == null ? LayoutlibVersion.NOT_SPECIFIED : 431 Integer.parseInt(propApi); 432 int llRev = propRev == null ? LayoutlibVersion.NOT_SPECIFIED : 433 Integer.parseInt(propRev); 434 if (llApi > LayoutlibVersion.NOT_SPECIFIED && 435 llRev >= LayoutlibVersion.NOT_SPECIFIED) { 436 layoutlibVersion = new LayoutlibVersion(llApi, llRev); 437 } 438 } catch (NumberFormatException e) { 439 // do nothing, we'll ignore the layoutlib version if it's invalid 440 } 441 442 // api number and name look valid, perform a few more checks 443 if (checkPlatformContent(platformFolder, log) == false) { 444 return null; 445 } 446 447 ISystemImage[] systemImages = 448 getPlatformSystemImages(sdkOsPath, platformFolder, apiNumber, apiCodename); 449 450 // create the target. 451 PlatformTarget target = new PlatformTarget( 452 sdkOsPath, 453 platformFolder.getAbsolutePath(), 454 apiNumber, 455 apiCodename, 456 apiName, 457 revision, 458 layoutlibVersion, 459 systemImages, 460 platformProp); 461 462 // need to parse the skins. 463 String[] skins = parseSkinFolder(target.getPath(IAndroidTarget.SKINS)); 464 target.setSkins(skins); 465 466 return target; 467 } else { 468 log.warning("Ignoring platform '%1$s': %2$s is missing.", //$NON-NLS-1$ 469 platformFolder.getName(), 470 SdkConstants.FN_BUILD_PROP); 471 } 472 473 return null; 474 } 475 476 /** 477 * Get all the system images supported by an add-on target. 478 * For an add-on, we first look for sub-folders in the addon/images directory. 479 * If none are found but the directory exists and is not empty, assume it's a legacy 480 * arm eabi system image. 481 * <p/> 482 * Note that it's OK for an add-on to have no system-images at all, since it can always 483 * rely on the ones from its base platform. 484 * 485 * @param root Root of the add-on target being loaded. 486 * @return an array of ISystemImage containing all the system images for the target. 487 * The list can be empty. 488 */ 489 private static ISystemImage[] getAddonSystemImages(File root) { 490 Set<ISystemImage> found = new TreeSet<ISystemImage>(); 491 492 root = new File(root, SdkConstants.OS_IMAGES_FOLDER); 493 File[] files = root.listFiles(); 494 boolean hasImgFiles = false; 495 496 if (files != null) { 497 // Look for sub-directories 498 for (File file : files) { 499 if (file.isDirectory()) { 500 found.add(new SystemImage( 501 file, 502 LocationType.IN_PLATFORM_SUBFOLDER, 503 file.getName())); 504 } else if (!hasImgFiles && file.isFile()) { 505 if (file.getName().endsWith(".img")) { //$NON-NLS-1$ 506 hasImgFiles = true; 507 } 508 } 509 } 510 } 511 512 if (found.size() == 0 && hasImgFiles && root.isDirectory()) { 513 // We found no sub-folder system images but it looks like the top directory 514 // has some img files in it. It must be a legacy ARM EABI system image folder. 515 found.add(new SystemImage( 516 root, 517 LocationType.IN_PLATFORM_LEGACY, 518 SdkConstants.ABI_ARMEABI)); 519 } 520 521 return found.toArray(new ISystemImage[found.size()]); 522 } 523 524 /** 525 * Get all the system images supported by a platform target. 526 * For a platform, we first look in the new sdk/system-images folders then we 527 * look for sub-folders in the platform/images directory and/or the one legacy 528 * folder. 529 * If any given API appears twice or more, the first occurrence wins. 530 * 531 * @param sdkOsPath The path to the SDK. 532 * @param root Root of the platform target being loaded. 533 * @param apiNumber API level of platform being loaded 534 * @param apiCodename Optional codename of platform being loaded 535 * @return an array of ISystemImage containing all the system images for the target. 536 * The list can be empty. 537 */ 538 private static ISystemImage[] getPlatformSystemImages( 539 String sdkOsPath, 540 File root, 541 int apiNumber, 542 String apiCodename) { 543 Set<ISystemImage> found = new TreeSet<ISystemImage>(); 544 Set<String> abiFound = new HashSet<String>(); 545 546 // First look in the SDK/system-image/platform-n/abi folders. 547 // We require/enforce the system image to have a valid properties file. 548 // The actual directory names are irrelevant. 549 // If we find multiple occurrences of the same platform/abi, the first one read wins. 550 551 AndroidVersion version = new AndroidVersion(apiNumber, apiCodename); 552 553 File[] firstLevelFiles = new File(sdkOsPath, SdkConstants.FD_SYSTEM_IMAGES).listFiles(); 554 if (firstLevelFiles != null) { 555 for (File firstLevel : firstLevelFiles) { 556 File[] secondLevelFiles = firstLevel.listFiles(); 557 if (secondLevelFiles != null) { 558 for (File secondLevel : secondLevelFiles) { 559 try { 560 File propFile = new File(secondLevel, SdkConstants.FN_SOURCE_PROP); 561 Properties props = new Properties(); 562 FileInputStream fis = null; 563 try { 564 fis = new FileInputStream(propFile); 565 props.load(fis); 566 } finally { 567 if (fis != null) { 568 fis.close(); 569 } 570 } 571 572 AndroidVersion propsVersion = new AndroidVersion(props); 573 if (!propsVersion.equals(version)) { 574 continue; 575 } 576 577 String abi = props.getProperty(PkgProps.SYS_IMG_ABI); 578 if (abi != null && !abiFound.contains(abi)) { 579 found.add(new SystemImage( 580 secondLevel, 581 LocationType.IN_SYSTEM_IMAGE, 582 abi)); 583 abiFound.add(abi); 584 } 585 } catch (Exception ignore) { 586 } 587 } 588 } 589 } 590 } 591 592 // Then look in either the platform/images/abi or the legacy folder 593 root = new File(root, SdkConstants.OS_IMAGES_FOLDER); 594 File[] files = root.listFiles(); 595 boolean useLegacy = true; 596 boolean hasImgFiles = false; 597 598 if (files != null) { 599 // Look for sub-directories 600 for (File file : files) { 601 if (file.isDirectory()) { 602 useLegacy = false; 603 String abi = file.getName(); 604 if (!abiFound.contains(abi)) { 605 found.add(new SystemImage( 606 file, 607 LocationType.IN_PLATFORM_SUBFOLDER, 608 abi)); 609 abiFound.add(abi); 610 } 611 } else if (!hasImgFiles && file.isFile()) { 612 if (file.getName().endsWith(".img")) { //$NON-NLS-1$ 613 hasImgFiles = true; 614 } 615 } 616 } 617 } 618 619 if (useLegacy && hasImgFiles && root.isDirectory() && 620 !abiFound.contains(SdkConstants.ABI_ARMEABI)) { 621 // We found no sub-folder system images but it looks like the top directory 622 // has some img files in it. It must be a legacy ARM EABI system image folder. 623 found.add(new SystemImage( 624 root, 625 LocationType.IN_PLATFORM_LEGACY, 626 SdkConstants.ABI_ARMEABI)); 627 } 628 629 return found.toArray(new ISystemImage[found.size()]); 630 } 631 632 /** 633 * Loads the Add-on from the SDK. 634 * Creates the "add-ons" folder if necessary. 635 * 636 * @param osSdkPath Location of the SDK 637 * @param list the list to fill with the add-ons. 638 * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null. 639 * @throws RuntimeException when the "add-ons" folder is missing and cannot be created. 640 */ 641 private static void loadAddOns(String osSdkPath, ArrayList<IAndroidTarget> list, ISdkLog log) { 642 File addonFolder = new File(osSdkPath, SdkConstants.FD_ADDONS); 643 644 if (addonFolder.isDirectory()) { 645 File[] addons = addonFolder.listFiles(); 646 647 IAndroidTarget[] targetList = list.toArray(new IAndroidTarget[list.size()]); 648 649 for (File addon : addons) { 650 // Add-ons have to be folders. Ignore files and no need to warn about them. 651 if (addon.isDirectory()) { 652 AddOnTarget target = loadAddon(addon, targetList, log); 653 if (target != null) { 654 list.add(target); 655 } 656 } 657 } 658 659 return; 660 } 661 662 // Try to create it or complain if something else is in the way. 663 if (!addonFolder.exists()) { 664 if (!addonFolder.mkdir()) { 665 throw new RuntimeException( 666 String.format("Failed to create %1$s.", 667 addonFolder.getAbsolutePath())); 668 } 669 } else { 670 throw new RuntimeException( 671 String.format("%1$s is not a folder.", 672 addonFolder.getAbsolutePath())); 673 } 674 } 675 676 /** 677 * Loads a specific Add-on at a given location. 678 * @param addonDir the location of the add-on directory. 679 * @param targetList The list of Android target that were already loaded from the SDK. 680 * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null. 681 */ 682 private static AddOnTarget loadAddon(File addonDir, 683 IAndroidTarget[] targetList, 684 ISdkLog log) { 685 686 // Parse the addon properties to ensure we can load it. 687 Pair<Map<String, String>, String> infos = parseAddonProperties(addonDir, targetList, log); 688 689 Map<String, String> propertyMap = infos.getFirst(); 690 String error = infos.getSecond(); 691 692 if (error != null) { 693 log.warning("Ignoring add-on '%1$s': %2$s", addonDir.getName(), error); 694 return null; 695 } 696 697 // Since error==null we're not supposed to encounter any issues loading this add-on. 698 try { 699 assert propertyMap != null; 700 701 String api = propertyMap.get(ADDON_API); 702 String name = propertyMap.get(ADDON_NAME); 703 String vendor = propertyMap.get(ADDON_VENDOR); 704 705 assert api != null; 706 assert name != null; 707 assert vendor != null; 708 709 PlatformTarget baseTarget = null; 710 711 // Look for a platform that has a matching api level or codename. 712 for (IAndroidTarget target : targetList) { 713 if (target.isPlatform() && target.getVersion().equals(api)) { 714 baseTarget = (PlatformTarget)target; 715 break; 716 } 717 } 718 719 assert baseTarget != null; 720 721 // get the optional description 722 String description = propertyMap.get(ADDON_DESCRIPTION); 723 724 // get the add-on revision 725 int revisionValue = 1; 726 String revision = propertyMap.get(ADDON_REVISION); 727 if (revision == null) { 728 revision = propertyMap.get(ADDON_REVISION_OLD); 729 } 730 if (revision != null) { 731 revisionValue = Integer.parseInt(revision); 732 } 733 734 // get the optional libraries 735 String librariesValue = propertyMap.get(ADDON_LIBRARIES); 736 Map<String, String[]> libMap = null; 737 738 if (librariesValue != null) { 739 librariesValue = librariesValue.trim(); 740 if (librariesValue.length() > 0) { 741 // split in the string into the libraries name 742 String[] libraries = librariesValue.split(";"); //$NON-NLS-1$ 743 if (libraries.length > 0) { 744 libMap = new HashMap<String, String[]>(); 745 for (String libName : libraries) { 746 libName = libName.trim(); 747 748 // get the library data from the properties 749 String libData = propertyMap.get(libName); 750 751 if (libData != null) { 752 // split the jar file from the description 753 Matcher m = PATTERN_LIB_DATA.matcher(libData); 754 if (m.matches()) { 755 libMap.put(libName, new String[] { 756 m.group(1), m.group(2) }); 757 } else { 758 log.warning( 759 "Ignoring library '%1$s', property value has wrong format\n\t%2$s", 760 libName, libData); 761 } 762 } else { 763 log.warning( 764 "Ignoring library '%1$s', missing property value", 765 libName, libData); 766 } 767 } 768 } 769 } 770 } 771 772 // get the abi list. 773 ISystemImage[] systemImages = getAddonSystemImages(addonDir); 774 775 // check whether the add-on provides its own rendering info/library. 776 boolean hasRenderingLibrary = false; 777 boolean hasRenderingResources = false; 778 779 File dataFolder = new File(addonDir, SdkConstants.FD_DATA); 780 if (dataFolder.isDirectory()) { 781 hasRenderingLibrary = new File(dataFolder, SdkConstants.FN_LAYOUTLIB_JAR).isFile(); 782 hasRenderingResources = new File(dataFolder, SdkConstants.FD_RES).isDirectory() && 783 new File(dataFolder, SdkConstants.FD_FONTS).isDirectory(); 784 } 785 786 AddOnTarget target = new AddOnTarget(addonDir.getAbsolutePath(), name, vendor, 787 revisionValue, description, systemImages, libMap, 788 hasRenderingLibrary, hasRenderingResources,baseTarget); 789 790 // need to parse the skins. 791 String[] skins = parseSkinFolder(target.getPath(IAndroidTarget.SKINS)); 792 793 // get the default skin, or take it from the base platform if needed. 794 String defaultSkin = propertyMap.get(ADDON_DEFAULT_SKIN); 795 if (defaultSkin == null) { 796 if (skins.length == 1) { 797 defaultSkin = skins[0]; 798 } else { 799 defaultSkin = baseTarget.getDefaultSkin(); 800 } 801 } 802 803 // get the USB ID (if available) 804 int usbVendorId = convertId(propertyMap.get(ADDON_USB_VENDOR)); 805 if (usbVendorId != IAndroidTarget.NO_USB_ID) { 806 target.setUsbVendorId(usbVendorId); 807 } 808 809 target.setSkins(skins, defaultSkin); 810 811 return target; 812 } 813 catch (Exception e) { 814 log.warning("Ignoring add-on '%1$s': error %2$s.", 815 addonDir.getName(), e.toString()); 816 } 817 818 return null; 819 } 820 821 /** 822 * Parses the add-on properties and decodes any error that occurs when loading an addon. 823 * 824 * @param addonDir the location of the addon directory. 825 * @param targetList The list of Android target that were already loaded from the SDK. 826 * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null. 827 * @return A pair with the property map and an error string. Both can be null but not at the 828 * same time. If a non-null error is present then the property map must be ignored. The error 829 * should be translatable as it might show up in the SdkManager UI. 830 */ 831 public static Pair<Map<String, String>, String> parseAddonProperties( 832 File addonDir, 833 IAndroidTarget[] targetList, 834 ISdkLog log) { 835 Map<String, String> propertyMap = null; 836 String error = null; 837 838 FileWrapper addOnManifest = new FileWrapper(addonDir, SdkConstants.FN_MANIFEST_INI); 839 840 do { 841 if (!addOnManifest.isFile()) { 842 error = String.format("File not found: %1$s", SdkConstants.FN_MANIFEST_INI); 843 break; 844 } 845 846 propertyMap = ProjectProperties.parsePropertyFile(addOnManifest, log); 847 if (propertyMap == null) { 848 error = String.format("Failed to parse properties from %1$s", 849 SdkConstants.FN_MANIFEST_INI); 850 break; 851 } 852 853 // look for some specific values in the map. 854 // we require name, vendor, and api 855 String name = propertyMap.get(ADDON_NAME); 856 if (name == null) { 857 error = addonManifestWarning(ADDON_NAME); 858 break; 859 } 860 861 String vendor = propertyMap.get(ADDON_VENDOR); 862 if (vendor == null) { 863 error = addonManifestWarning(ADDON_VENDOR); 864 break; 865 } 866 867 String api = propertyMap.get(ADDON_API); 868 PlatformTarget baseTarget = null; 869 if (api == null) { 870 error = addonManifestWarning(ADDON_API); 871 break; 872 } 873 874 // Look for a platform that has a matching api level or codename. 875 for (IAndroidTarget target : targetList) { 876 if (target.isPlatform() && target.getVersion().equals(api)) { 877 baseTarget = (PlatformTarget)target; 878 break; 879 } 880 } 881 882 if (baseTarget == null) { 883 error = String.format("Unable to find base platform with API level '%1$s'", api); 884 break; 885 } 886 887 // get the add-on revision 888 String revision = propertyMap.get(ADDON_REVISION); 889 if (revision == null) { 890 revision = propertyMap.get(ADDON_REVISION_OLD); 891 } 892 if (revision != null) { 893 try { 894 Integer.parseInt(revision); 895 } catch (NumberFormatException e) { 896 // looks like revision does not parse to a number. 897 error = String.format("%1$s is not a valid number in %2$s.", 898 ADDON_REVISION, SdkConstants.FN_BUILD_PROP); 899 break; 900 } 901 } 902 903 } while(false); 904 905 return Pair.of(propertyMap, error); 906 } 907 908 /** 909 * Converts a string representation of an hexadecimal ID into an int. 910 * @param value the string to convert. 911 * @return the int value, or {@link IAndroidTarget#NO_USB_ID} if the convertion failed. 912 */ 913 private static int convertId(String value) { 914 if (value != null && value.length() > 0) { 915 if (PATTERN_USB_IDS.matcher(value).matches()) { 916 String v = value.substring(2); 917 try { 918 return Integer.parseInt(v, 16); 919 } catch (NumberFormatException e) { 920 // this shouldn't happen since we check the pattern above, but this is safer. 921 // the method will return 0 below. 922 } 923 } 924 } 925 926 return IAndroidTarget.NO_USB_ID; 927 } 928 929 /** 930 * Prepares a warning about the addon being ignored due to a missing manifest value. 931 * This string will show up in the SdkManager UI. 932 * 933 * @param valueName The missing manifest value, for display. 934 */ 935 private static String addonManifestWarning(String valueName) { 936 return String.format("'%1$s' is missing from %2$s.", 937 valueName, SdkConstants.FN_MANIFEST_INI); 938 } 939 940 /** 941 * Checks the given platform has all the required files, and returns true if they are all 942 * present. 943 * <p/>This checks the presence of the following files: android.jar, framework.aidl, aapt(.exe), 944 * aidl(.exe), dx(.bat), and dx.jar 945 * 946 * @param platform The folder containing the platform. 947 * @param log Logger. Cannot be null. 948 */ 949 private static boolean checkPlatformContent(File platform, ISdkLog log) { 950 for (String relativePath : sPlatformContentList) { 951 File f = new File(platform, relativePath); 952 if (!f.exists()) { 953 log.warning( 954 "Ignoring platform '%1$s': %2$s is missing.", //$NON-NLS-1$ 955 platform.getName(), relativePath); 956 return false; 957 } 958 } 959 return true; 960 } 961 962 963 964 /** 965 * Parses the skin folder and builds the skin list. 966 * @param osPath The path of the skin root folder. 967 */ 968 private static String[] parseSkinFolder(String osPath) { 969 File skinRootFolder = new File(osPath); 970 971 if (skinRootFolder.isDirectory()) { 972 ArrayList<String> skinList = new ArrayList<String>(); 973 974 File[] files = skinRootFolder.listFiles(); 975 976 for (File skinFolder : files) { 977 if (skinFolder.isDirectory()) { 978 // check for layout file 979 File layout = new File(skinFolder, SdkConstants.FN_SKIN_LAYOUT); 980 981 if (layout.isFile()) { 982 // for now we don't parse the content of the layout and 983 // simply add the directory to the list. 984 skinList.add(skinFolder.getName()); 985 } 986 } 987 } 988 989 return skinList.toArray(new String[skinList.size()]); 990 } 991 992 return new String[0]; 993 } 994 995 /** 996 * Loads all samples from the {@link SdkConstants#FD_SAMPLES} directory. 997 * 998 * @param log Logger. Cannot be null. 999 */ 1000 private void loadSamples(ISdkLog log) { 1001 File sampleFolder = new File(mOsSdkPath, SdkConstants.FD_SAMPLES); 1002 if (sampleFolder.isDirectory()) { 1003 File[] platforms = sampleFolder.listFiles(); 1004 1005 for (File platform : platforms) { 1006 if (platform.isDirectory()) { 1007 // load the source.properties file and get an AndroidVersion object from it. 1008 AndroidVersion version = getSamplesVersion(platform, log); 1009 1010 if (version != null) { 1011 // locate the platform matching this version 1012 for (IAndroidTarget target : mTargets) { 1013 if (target.isPlatform() && target.getVersion().equals(version)) { 1014 ((PlatformTarget)target).setSamplesPath(platform.getAbsolutePath()); 1015 break; 1016 } 1017 } 1018 } 1019 } 1020 } 1021 } 1022 } 1023 1024 /** 1025 * Returns the {@link AndroidVersion} of the sample in the given folder. 1026 * 1027 * @param folder The sample's folder. 1028 * @param log Logger for errors. Cannot be null. 1029 * @return An {@link AndroidVersion} or null on error. 1030 */ 1031 private AndroidVersion getSamplesVersion(File folder, ISdkLog log) { 1032 File sourceProp = new File(folder, SdkConstants.FN_SOURCE_PROP); 1033 try { 1034 Properties p = new Properties(); 1035 FileInputStream fis = null; 1036 try { 1037 fis = new FileInputStream(sourceProp); 1038 p.load(fis); 1039 } finally { 1040 if (fis != null) { 1041 fis.close(); 1042 } 1043 } 1044 1045 return new AndroidVersion(p); 1046 } catch (FileNotFoundException e) { 1047 log.warning("Ignoring sample '%1$s': does not contain %2$s.", //$NON-NLS-1$ 1048 folder.getName(), SdkConstants.FN_SOURCE_PROP); 1049 } catch (IOException e) { 1050 log.warning("Ignoring sample '%1$s': failed reading %2$s.", //$NON-NLS-1$ 1051 folder.getName(), SdkConstants.FN_SOURCE_PROP); 1052 } catch (AndroidVersionException e) { 1053 log.warning("Ignoring sample '%1$s': no android version found in %2$s.", //$NON-NLS-1$ 1054 folder.getName(), SdkConstants.FN_SOURCE_PROP); 1055 } 1056 1057 return null; 1058 } 1059 1060 } 1061