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